Python(boto3)中的 DynamoDB TransactWriteItems

交易是 boto3 兩套 API 分歧最劇烈的地方之一:transact_write_items 只存在於低階 client 上,所以 Table 給你的那種原生 Python 型別便利,在這裡是沒有的。而當交易失敗時,你需要的東西藏在例外的一個角落,大多數 boto3 程式碼從來不會去看。(交易替你買到了什麼在每一套 SDK 裡都一樣。)

程式碼

import boto3

client = boto3.client("dynamodb")

# Move one award between two songs — atomically. If the first song has no
# award to give, NEITHER update happens.
try:
    client.transact_write_items(
        TransactItems=[
            {
                "Update": {
                    "TableName": "Music",
                    "Key": {"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "Cubano Chant"}},
                    "UpdateExpression": "SET #upd0 = #upd0 - :one",
                    "ConditionExpression": "#upd0 >= :one",
                    "ExpressionAttributeNames": {"#upd0": "Awards"},
                    "ExpressionAttributeValues": {":one": {"N": "1"}},
                }
            },
            {
                "Update": {
                    "TableName": "Music",
                    "Key": {"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "A Mis Abuelos"}},
                    "UpdateExpression": "SET #upd0 = if_not_exists(#upd0, :zero) + :one",
                    "ExpressionAttributeNames": {"#upd0": "Awards"},
                    "ExpressionAttributeValues": {":one": {"N": "1"}, ":zero": {"N": "0"}},
                }
            },
        ]
    )
    print("Transaction committed")
except client.exceptions.TransactionCanceledException as e:
    # One reason per action, in TransactItems order. Code "None" means that
    # action was fine — some OTHER action sank the transaction.
    codes = [reason["Code"] for reason in e.response["CancellationReasons"]]
    print(f"Transaction canceled: {codes}")  # e.g. ['ConditionalCheckFailed', 'None']

說明

  • TransactItems — 一份由 PutUpdateDeleteConditionCheck 字典組成的清單,每一個值都是 DynamoDB JSON,沒有例外。這是唯一一個型別化形式並非可選的 boto3 呼叫,也是本頁最後那一節存在的原因。上限請見 CLI 那一頁
  • CancellationReasons 不在 Error 裡面。botocore 會把模型化的錯誤欄位提到回應字典的最上層,所以被攔下的例外帶著的 e.response 裡,CancellationReasonsErrorMessageResponseMetadata 是並排的。去 e.response["Error"] 底下找是找不到的,而 e.response["Error"] 只放摘要用的代碼與訊息。
  • None 那幾筆沒有 "Message" — 成功動作的原因是只有一個鍵的字典 {"Code": "None"},所以順手寫的 [r["Message"] for r in reasons] 剛好會在那些成功的動作上丟出 KeyError: 'Message'。請用 r.get("Message")
  • 一個動態產生的例外類別 — botocore 會在執行期依服務模型建出 client.exceptions.TransactionCanceledException,這就是為什麼它掛在 client 實例上,也是為什麼你沒辦法 from botocore.exceptions import ... 匯入它。在一個作用域裡沒有 client 的輔助函式中,請攔 botocore.exceptions.ClientError 並依 e.response["Error"]["Code"] 分支;那個產生出來的類別是它的子類別。
  • 結構性的錯誤不是以取消的形式抵達的,所以程式碼裡的 except 從來不會看到它們。兩個動作瞄準同一個項目時,丟出的是一個代碼為 ValidationException 的裸 ClientError,而它的 e.response 裡沒有 CancellationReasons 這個鍵,因為這筆交易在任何動作執行之前就被回絕了。如果你想用同樣的上下文把那些也記錄下來,請在外層攔 ClientError
  • 在某個動作上加 ReturnValuesOnConditionCheckFailure: "ALL_OLD",會把落敗的項目以 DynamoDB JSON 放進那個動作的原因裡的 Item 鍵下,省下你在已經輸掉競賽之後還要再補一次 get_item
  • boto3 會替你填 ClientRequestToken。在線路上擷取到的結果是,兩次一模一樣的 transact_write_items 呼叫帶著兩個不同的 UUID 離開,所以這個 token 覆蓋的是單一次呼叫,而不是你自己的攔截重試迴圈。如果重試可能活得比行程還久,請自己傳一個穩定的 token。
  • TransactionConflict 要重試,ConditionalCheckFailed 絕不重試 — 前者是說有別人短暫地佔住了那個項目;後者是說你的前提條件是假的,而且下一次還會是假的。這是大多數處理器唯一需要分開的兩個代碼,完整的一組則在 TransactionCanceledException 頁面上解碼
  • 成本 — 交易式寫入的計費大約是同一筆寫入在交易外的兩倍,在 CLI 那一頁量過。如果你只需要在單一項目上取得不可分割性,條件式寫入以一半的價格就能買到。

這件事沒有 resource API 的版本

boto3.resource("dynamodb").Table(...) 沒有 transact_write_items 這個屬性;只有 resource.meta.client 才有。所以一份已經定案用 Table 加原生 Python 型別的程式碼庫,得為了交易退回型別化的 DynamoDB JSON,或用 boto3.dynamodb.types.TypeSerializer 手動序列化:

from boto3.dynamodb.types import TypeSerializer

serialize = TypeSerializer().serialize
values = {k: serialize(v) for k, v in {":one": 1, ":zero": 0}.items()}

TypeSerializer 套用的規則跟 resource API 一樣,也就是說它會拒絕 float,並且對任何帶小數的東西都要求 decimal.Decimal。當你只是想把一個字面值貼進腳本時,DynamoDB JSON 轉換器會在瀏覽器裡做同樣的轉換。想在不手寫這兩種形式的情況下編輯交易碰到的那些項目,請下載 DynoTable

相關範例

參考資料

最後驗證於 2026-07-28,對照上方連結的 AWS 官方文件。

不必透過主控台就能操作 DynamoDB

一款快速的 DynamoDB 桌面用戶端,可執行 DynamoDB 無法執行的真正 SQL — JOINs、GROUP BY、聚合 — 並支援視覺化編輯與使用你自己的 Bedrock 金鑰的 AI 代理。

30 天免費試用,無需信用卡 — 之後為無時間限制的免費方案。