Python(boto3)中的 DynamoDB 條件寫入
boto3 是唯一一個條件寫入有具名例外類別可以攔截的 SDK,同時也是唯一一個把回傳項目藏在你猜不到的位置的 SDK。運算式本身在哪裡都一樣運作;DynamoDB 條件運算式談的是那些函式與樂觀鎖模式。
程式碼
import boto3
client = boto3.client("dynamodb")
# Update the item only if nobody changed it since we read version 7.
try:
client.update_item(
TableName="Music",
Key={"Artist": {"S": "Arturo Sandoval"}, "SongTitle": {"S": "Cubano Chant"}},
UpdateExpression="SET #upd0 = :updValue0, #version = :newVersion",
ConditionExpression="attribute_exists(#cond0) AND #version = :expectedVersion",
ExpressionAttributeNames={"#upd0": "Genre", "#version": "Version", "#cond0": "Artist"},
ExpressionAttributeValues={
":updValue0": {"S": "Latin Jazz"},
":expectedVersion": {"N": "7"},
":newVersion": {"N": "8"},
},
ReturnValuesOnConditionCheckFailure="ALL_OLD",
)
print("Updated to version 8")
except client.exceptions.ConditionalCheckFailedException as e:
# With ReturnValuesOnConditionCheckFailure="ALL_OLD", the current item
# rides back on the exception — no extra read to see what beat you.
print("Lost the race — item is now:", e.response.get("Item"))說明
ConditionalCheckFailedException是有模型定義的類別,所以except client.exceptions.…行得通。多數 DynamoDB 錯誤並非如此:ValidationException根本沒有對應類別,只能靠e.response["Error"]["Code"]比對。這個有模型的類別仍然繼承自ClientError,所以若你把處理順序排得隨便,上游一個籠統的except ClientError就會把它吞掉。- 回傳的項目是
e.response的頂層 key,不是e.response["Error"]底下的。這就是為什麼程式碼寫的是e.response.get("Item")。很容易跑去["Error"]裡跟Code與Message一起找,結果什麼都沒找到,然後斷定這個參數沒作用。 - 項目回來時是 DynamoDB JSON,即使你可能已經習慣原生值,因為這是低階用戶端。若你想要單純的 Python 值,
boto3.dynamodb.types.TypeDeserializer可以轉換它。 - 資源 API 用物件表達同一個防護:
ConditionExpression=Attr("Version").eq(7) & Attr("Artist").exists(),使用原生值、不需佔位符對應。它拋出的是完全相同的例外,所以下面的處理方式不變。 - 檢查失敗照樣計費一次寫入。開發人員指南明確說明:條件為 false 仍會消耗寫入容量,並以新舊項目中較大的那一個計算大小,所以在爭用的鍵上無上限地重試會花掉真金白銀卻毫無進展。
boto3 把回傳的項目放在哪裡
對一個已存 Version 為 9 的項目執行上面的程式碼,然後印出例外回應的 keys。DynamoDB Local 3.3.0、boto3 1.43.58:
sorted(e.response.keys()) -> ['Error', 'Item', 'ResponseMetadata']
e.response["Item"] -> {'Artist': {'S': 'Arturo Sandoval'},
'Year': {'N': '1994'},
'Version': {'N': '9'},
'SongTitle': {'S': 'Cubano Chant'},
'AlbumTitle': {'S': 'Danzon'}}拿掉 ReturnValuesOnConditionCheckFailure,同一次失敗會給出 ['Error', 'ResponseMetadata']。Item 這個 key 不存在,而 e.response.get("Item") 會回傳 None 而不是拋錯。這正是那種能通過程式碼審查、然後開始在正式環境記錄 None 的錯誤版本。
為什麼運算式裡每個名稱都要別名
程式碼寫的是 #version 與 #cond0 而不是 Version 與 Artist,對兩個平凡的英文字來說看起來太過頭了。對這兩個字來說確實如此。Version 不是 DynamoDB 保留字,直接寫也能通過名稱驗證。
Year 是保留字,而同一張表格裡就有一個。直接對它下防護,你會得到:
ValidationException: Invalid ConditionExpression: Attribute name is a reserved keyword;
reserved keyword: Year那份清單上有 573 個字,包括 Name、Status、Size、Count、Data、Owner、Timestamp 與 Items。把所有名稱都別名化,正是產生器產出的程式碼永遠不必分辨誰是誰的方法。把你的屬性名稱貼進保留字檢查器,它會替需要的那些回傳 ExpressionAttributeNames 對應。
若想在自己的表格上寫這些防護、又讓別名化自動處理好,請下載 DynoTable。
相關範例
- Node.js 中的 DynamoDB 條件寫入 — 以 AWS SDK v3 做同一種樂觀鎖。
- 使用 AWS CLI 的 DynamoDB 條件寫入 — 從 shell 做同一種樂觀鎖。
- Python 中的 DynamoDB PutItem — 僅建立的
attribute_not_existsput。 - DynamoDB 條件運算式 — 每一個函式,附使用模式。
- 對多個屬性強制唯一性 — 條件與交易併用。
- DynamoDB ConditionalCheckFailedException — 當檢查失敗是預期之內時,該如何低成本處理。
參考資料
- UpdateItem — Amazon DynamoDB API Reference
- DynamoDB.Client.update_item — Boto3 documentation
- Condition expressions — Amazon DynamoDB Developer Guide
- DynamoDB read and write operations (capacity unit consumption) — Amazon DynamoDB Developer Guide
- Reserved words in DynamoDB — Amazon DynamoDB Developer Guide
最後驗證於 2026-07-28,對照上方連結的 AWS 官方文件。