Node.js 中的 DynamoDB 條件寫入(AWS SDK v3)
在 AWS SDK v3 中,條件寫入有意思的部分不是 ConditionExpression — 它到哪裡都一樣運作,而且 DynamoDB 條件運算式已經談過。有意思的是失敗路徑:如果你要求了,v3 會把落敗的那個項目附在拋出的錯誤上交給你;如果你沒要求,就什麼都拿不到。
程式碼
import {DynamoDBClient, UpdateItemCommand} from '@aws-sdk/client-dynamodb';
const client = new DynamoDBClient({region: 'us-east-1'});
// Update the item only if nobody changed it since we read version 7.
const command = new UpdateItemCommand({
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'
});
try {
await client.send(command);
console.log('Updated to version 8');
} catch (err) {
if (err.name === 'ConditionalCheckFailedException') {
// With ReturnValuesOnConditionCheckFailure: 'ALL_OLD', the current item
// rides back on the exception — no extra read to see what beat you.
console.log('Lost the race — item is now:', err.Item);
} else {
throw err;
}
}說明
- 檢查失敗是一個被拋出的錯誤,不是一個狀態欄位。v3 會 reject 那個 promise,所以寫入路徑與競賽落敗路徑是不同的分支。
err.name === 'ConditionalCheckFailedException'是那個判別依據;其他任何東西都必須重新拋出,那正是程式碼裡else的用途。把整個catch吞掉,你就悄悄把一次節流變成了什麼都沒做。 ReturnValuesOnConditionCheckFailure是你看見誰贏過你的唯一辦法。少了它,錯誤只帶著訊息、別無其他,而你又得回頭做一次本來不需要的GetItem。API 參考把它的有效值定為ALL_OLD | NONE,並確認它不消耗讀取容量。err.Item是一個原始的AttributeValue對應,形狀和你送出的Key一樣,不是普通的 JavaScript 物件。在把Version拿去和數字比較之前,先讓它通過@aws-sdk/util-dynamodb的unmarshall,否則你會是在跟{N: '9'}比較。- 失敗的寫入照樣計費。開發人員指南明說條件判定為 false 仍會消耗寫入容量,並以新舊項目中較大的那個計算大小。針對熱鍵的重試迴圈在帳單上是真實的一行,所以請設上限。
- 程式碼裡每個名稱都做了別名(
#version→Version、#cond0→Artist),因為產生它的 Expression Builder 一律做別名。在這裡那比必要的更重,但永遠不會錯,這就是它做的取捨。
從例外上讀出落敗者的副本
把儲存的 Version 設成 9,再執行那段期待 7 的程式碼。DynamoDB Local 3.3.0 會拋出例外,而被捕捉到的錯誤帶著:
err.name ConditionalCheckFailedException
err.message The conditional request failed
err.$metadata.httpStatusCode 400
err.Item {
Artist: { S: 'Arturo Sandoval' },
Year: { N: '1994' },
Version: { N: '9' },
SongTitle: { S: 'Cubano Chant' },
AlbumTitle: { S: 'Danzon' }
}那個 Version: 9 就是重點所在。重試可以直接把 :expectedVersion 設成 9 再走一次更新,不需要多一次讀取,也不會留下讓第三個寫入者在你的 GetItem 與重試之間插進來的空隙。
從同一個命令中刪掉 ReturnValuesOnConditionCheckFailure 再跑一次。同樣的 name、同樣的 message、同樣的 400,而 err.Item 是 undefined。沒有任何東西會警告你:這個參數是選用的,它不存在不是錯誤,而讀取 err.Item 的程式碼只會開始在生產環境裡記錄 undefined。
也請注意,這裡的 400 並不代表請求格式錯誤。ValidationException 與 ConditionalCheckFailedException 共用同一個狀態碼,而其中只有一個是 bug,這正是為什麼分支要看 err.name 而永遠不看狀態碼。
想針對你自己的資料看著條件成功與失敗,而且運算式是替你寫好的而不是打出來的,就下載 DynoTable。
相關範例
- Python 中的 DynamoDB 條件寫入 — 以 boto3 做同一個樂觀鎖。
- 以 AWS CLI 執行 DynamoDB 條件寫入 — 從 shell 做同一個樂觀鎖。
- Node.js 中的 DynamoDB PutItem — 僅建立用的
attribute_not_existsput。 - DynamoDB 條件運算式 — 每一個函式,附帶模式。
- 在多個屬性上強制唯一性 — 條件與交易的組合。
- DynamoDB ConditionalCheckFailedException — 當檢查失敗是預期中的事,以及如何低成本地處理它。
參考資料
- UpdateItem — Amazon DynamoDB API Reference
- Condition expressions — Amazon DynamoDB Developer Guide
- DynamoDB read and write operations (capacity unit consumption) — Amazon DynamoDB Developer Guide
最後查證於 2026-07-28,對照上方連結的官方 AWS 文件。