Node.js 中的 DynamoDB UpdateItem(AWS SDK v3)
低階的 v3 client 在兩個方向上都說 DynamoDB JSON,這代表你送出的每個數字與收回的每個數字都是字串。那不是瑕疵;那是一個 38 位數的 DynamoDB 數字,能在一個唯一數值型別是 double 的語言裡存活下來的唯一辦法。那也正是 bug 所在之處。
程式碼
import {DynamoDBClient, UpdateItemCommand} from '@aws-sdk/client-dynamodb';
const client = new DynamoDBClient({region: 'us-east-1'});
const command = new UpdateItemCommand({
TableName: 'Music',
Key: {
Artist: {S: 'Arturo Sandoval'},
SongTitle: {S: 'Cubano Chant'}
},
UpdateExpression: 'SET #upd0 = :updValue0, #upd1 = :updValue1 ADD #upd2 :updValue2',
ExpressionAttributeNames: {
'#upd0': 'Genre',
'#upd1': 'Year',
'#upd2': 'Awards'
},
ExpressionAttributeValues: {
':updValue0': {S: 'Latin Jazz'},
':updValue1': {N: '1994'},
':updValue2': {N: '1'}
},
ReturnValues: 'ALL_NEW'
});
const response = await client.send(command);
console.log(response.Attributes); // the item after the update針對一個原本沒有 Genre 也沒有 Awards 的項目,response.Attributes 回來時是:
{"Artist":{"S":"Arturo Sandoval"},"Awards":{"N":"1"},"Genre":{"S":"Latin Jazz"},"Year":{"N":"1994"},"SongTitle":{"S":"Cubano Chant"}}typeof response.Attributes.Awards.N 是 "string",所以 response.Attributes.Awards.N + 1 求值成 "11"。沒有東西拋出例外、沒有東西警告你,而那個錯的數字就進了你的下一次寫入。請在邊界處剖析:Number(response.Attributes.Awards.N)。
說明
運算式是一個純字串,而 v3 不會替你檢查它。
UpdateItemCommand驗證的是輸入物件的形狀,從不驗證UpdateExpression裡的文法,所以一個打字錯誤就是一次往返加一個 400。文法在更新運算式裡;ADD #upd2 :updValue2是那個原子遞增,而加上ConditionExpression: 'attribute_exists(Artist)'會讓這個呼叫只做更新,而不是 upsert。ReturnValues: 'UPDATED_NEW'通常才是你要的那一個。同一次更新會回傳{"Awards":{"N":"2"}},別無其他。ALL_NEW則在每次呼叫時把整個項目送回來,在一個肥大的項目上,那是你為了讀一個計數器而付的頻寬。$metadata是 v3 的頻外通道:{"httpStatusCode":200,"requestId":"...","attempts":1,"totalRetryDelay":0}。attempts是「這個有沒有重試過」的誠實答案,而當你在推敲一次非冪等的寫入是否跑了兩次時,那很重要。ValidationException不是一個你能 catch 的類別,只是一個你能比較的name。少一個別名時,它回來的是err.name === 'ValidationException',而err.message被設成Invalid UpdateExpression: Attribute name is a reserved keyword; reserved keyword: Year。document client 是另一種取捨。
@aws-sdk/lib-dynamodb接受原生的 JS 值並替回應做 unmarshall,代價是失去那份字串安全。來自@aws-sdk/util-dynamodb的marshall({awards: 9007199254740993})會直接拒絕:Number 9007199254740992 is greater than Number.MAX_SAFE_INTEGER. Use NumberValue from @aws-sdk/lib-dynamodb.仔細看那個訊息裡的數字。它結尾是
2,不是字面值裡寫的那個3:JavaScript 在 SDK 看到它之前就已經把它四捨五入了。這段程式碼裡的低階 client 不可能有那個問題,因為{N: '9007199254740993'}一路到傳輸線上都是文字。
一個失敗的條件會交給你什麼
在輸入中加上 ReturnValuesOnConditionCheckFailure: 'ALL_OLD',被拋出的錯誤就會帶著那個贏過你的項目:
name: ConditionalCheckFailedException | message: "The conditional request failed" | http: 400
err.Item: {"Artist":{"S":"Arturo Sandoval"},"Awards":{"N":"2"},"Year":{"N":"1994"},"SongTitle":{"S":"Cubano Chant"},"Genre":{"S":"Latin Jazz"}}不論是哪個 client 拋出的,err.Item 都是原始的 DynamoDB JSON,而且是免費的。少了它,要誠實查出一次樂觀並行更新為何失敗,就得再補一次 GetItem — 那要花一次讀取,而且可能已經又過期了。
DynamoDB JSON 轉換器會把那份酬載變成一個純 JS 物件,也能轉回去,那是從一個真實項目做出測試樣本最快的方式。至於一開始要怎麼從線上資料表把那個項目撈出來,就下載 DynoTable。
相關指南
- DynamoDB 更新運算式 —
SET、ADD、REMOVE、DELETE與慣用寫法。 - 認識 ReturnValues — 每個
ReturnValues選項各給你什麼。 - 「Attribute name is a reserved keyword」 — 為什麼這裡的別名對應不是選用的。
- 「Invalid UpdateExpression」語法錯誤 — 常見的 SET/ADD 語法錯誤解讀。
參考資料
- UpdateItem — Amazon DynamoDB API Reference
- UpdateItemCommand — AWS SDK for JavaScript v3 Reference
- Update expressions — Amazon DynamoDB Developer Guide
最後查證於 2026-07-28,對照上方連結的官方 AWS 文件。