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-dynamodbmarshall({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

相關指南

參考資料

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

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

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

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