Node.js 中的 DynamoDB TransactWriteItems(AWS SDK v3)

一次成功的 TransactWriteItemsCommand 幾乎什麼都不告訴你:沒有項目、沒有屬性,一個空回應。你需要的一切都在例外上,所以在 SDK v3 中,底下那個 catch 區塊才是真正的 API 表面,而確切知道什麼會落進去是值得的。(至於交易究竟何時才是對的做法,請見 DynamoDB 交易。)

程式碼

import {DynamoDBClient, TransactWriteItemsCommand} from '@aws-sdk/client-dynamodb';

const client = new DynamoDBClient({region: 'us-east-1'});

// Move one award between two songs — atomically. If the first song has no
// award to give, NEITHER update happens.
const command = new TransactWriteItemsCommand({
  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'}}
      }
    }
  ]
});

try {
  await client.send(command);
  console.log('Transaction committed');
} catch (err) {
  if (err.name === 'TransactionCanceledException') {
    // One reason per action, in TransactItems order. 'None' means that action
    // was fine — some OTHER action sank the transaction.
    const codes = (err.CancellationReasons ?? []).map((r) => r.Code);
    console.log('Transaction canceled:', codes); // e.g. ['ConditionalCheckFailed', 'None']
  } else {
    throw err;
  }
}

說明

  • TransactItems — 一個由 PutUpdateDeleteConditionCheck 動作組成的有序陣列。順序不是執行順序(交易是原子性的),但它_確實_是失敗原因回來時的順序,而那是唯一需要在意它的理由。上限在下面談。
  • v3 實際拋出的東西。被捕捉到的物件自有的屬性是 $fault$retryable$metadatanameCancellationReasonsmessage__type。沒有 err.codeerr.name 才是你要用來分支的字串,而 err.$metadata 帶著 httpStatusCode: 400 加上 attempts: 1,那正是你判斷 SDK 沒有悄悄替你重試那次取消的依據。
  • CancellationReasons 是依位置對應且稀疏的。對上面那筆交易,它回來時是 [{"Code":"ConditionalCheckFailed","Message":"The conditional request failed"},{"Code":"None"}]。那個 None 項目根本沒有 Message 屬性,所以 err.CancellationReasons.map((r) => r.Message.trim()) 會在你的錯誤處理器裡,正好對那些成功的動作拋出例外。
  • ReturnValuesOnConditionCheckFailure: 'ALL_OLD' 會在那個動作的原因裡加上一個 Item,排在 CodeMessage 之前,格式是原始的 DynamoDB JSON。落敗項目的屬性免費回來;另一個選擇則是在你已經輸掉競賽之後再補一次 GetItem
  • err.name 的檢查有個漏洞,而知道是哪一個很值得。把兩個動作指向同一個項目,DynamoDB 會回以 ValidationException,訊息是 Transaction request cannot include multiple operations on one item,而且完全沒有 CancellationReasons,因為什麼都沒被嘗試。上面的 else { throw err } 分支會把它重新拋出。那是正確行為,不是 bug,但這也意味著結構性的錯誤永遠不會抵達你的取消記錄。
  • v3 已經在送 ClientRequestToken,即使你省略它。擷取序列化後的主體會看到傳輸線上有一個全新的 UUID,而同一個命令物件的兩次 send() 帶著兩個不同的 token 出去。所以那個 token 保護的是一次進行中的呼叫,而不是你自己的重試迴圈:捕捉、重送,你就有了新的 token 而沒有冪等性。如果重試可能跨越行程邊界,請自己提供一個。改了任何參數還沿用同一個,你會得到 IdempotentParameterMismatch,而不是一次無聲的重複套用。
  • 只有另外一個代碼需要專屬的程式碼路徑TransactionConflict 代表有另一筆並行交易握住了你的某個項目,所以帶退避的重試才是對的回應,而 ConditionalCheckFailed 則永遠不是。其餘的都在 TransactionCanceledException 頁面上解碼
  • 成本 — 交易中的每個項目在底下都會被寫兩次(準備,然後提交),所以請以一般寫入約 2 倍的寫入容量來編列預算。單一項目的條件寫入只花一半,就能給你一個項目上的原子性。

你會先撞上哪一個上限

100 個動作的上限與 4 MB 的上限是各自獨立的,而讓人意外的是位元組那一個:一百次計數器遞增不算什麼,而十來個肥大的項目光憑自己就能耗光總量。在決定要把多少動作打成一批之前,先用 DynamoDB 項目大小計算機量一個具代表性的項目。想在還在寫條件的時候就先讀出某個動作會碰到的項目,就下載 DynoTable

相關範例

參考資料

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

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

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

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