DynamoDB 交易
DynamoDB 交易把數筆寫入組成單一「全有或全無」的操作:要嘛每個動作都提交,要嘛全都不提交。這就是當一次寫入半途失敗時,你讓兩個相關項目不至於彼此偏離的方法。
在稽核記錄的情境中,你附加的每個事件也應該讓某個依租戶的 eventCount 加一。如果事件落地但計數器沒有 — 或反過來 — 那麼記錄與計數就會永遠不一致。一筆交易讓這不可能發生。
DynamoDB 支援交易嗎?
是的。DynamoDB 透過 TransactWriteItems 與 TransactGetItems 支援 ACID 交易,可將最多 100 個動作組成橫跨一或多張表格的單一全有或全無操作。要嘛每筆寫入都提交,要嘛全都不提交,因此相關的項目不會彼此偏離。交易式寫入消耗的容量是一般寫入的兩倍,而條件失敗或衝突會取消整個請求。
TransactWriteItems把最多 100 個寫入動作組起來,橫跨一或多張表格,全有或全無。彙總的項目大小不能超過 4 MB。- 動作有
Put、Update、Delete與ConditionCheck。ConditionCheck對一個你並未寫入的項目做出斷言。 - 它的成本是雙倍。 一次交易式寫入消耗的容量是一般寫入的兩倍 — DynamoDB 會先準備再提交。
- 衝突與失敗的條件會取消整筆操作,並回傳
TransactionCanceledException;不會留下任何部分完成的東西。
問題:兩筆必須一致的寫入
你希望每個新的稽核事件也讓該租戶的累計計數加一。若做成兩次獨立的呼叫,兩者之間的任何失敗都會損毀你的資料:
PutItem寫入新的EVENT#…項目 — 成功。UpdateItem執行ADD eventCount 1— 逾時。
現在記錄比計數器所宣稱的多了一列。盲目重試步驟 2 有重複計數的風險;不重試則讓它們不一致。沒有安全的復原方式,因為這兩筆寫入從未被連結在一起。
從 SQL 過來,你會把兩者包進 BEGIN … COMMIT。DynamoDB 的答案是一個單一交易式請求,把兩筆寫入一起攜帶。
TransactWriteItems 如何運作
根據
AWS 開發人員指南,
TransactWriteItems「把最多 100 個寫入動作組成單一全有或全無的操作」,目標最多 100 個不同的項目,而且「交易中各項目的彙總大小不能超過 4 MB」。這些動作會原子式地完成 — 全部成功或全部不做。
你可以在一筆交易中混用四種動作類型:
Put— 建立或取代一個項目。Update— 編輯屬性(包括我們計數器用到的ADD)。Delete— 依鍵移除一個項目。ConditionCheck— 對一個你並未另行寫入的項目斷言某個條件(例如「此租戶仍為使用中」)。
實務上還有兩條規則會咬人。第一,交易消耗的容量是等效非交易式寫入的兩倍 — DynamoDB 會做一個準備階段與一個提交階段。第二,你不能在一筆交易中對同一項目下手兩次,且交易不能對 index 執行。
一個實作範例:附加+計數,原子式地
回到稽核記錄。為租戶 acme 附加一個事件並讓其計數器加一,是一筆有兩個動作的交易:
| action | item | effect | |
|---|---|---|---|
| Put | TENANT#acme | EVENT#2026-06-24T09:14Z#a1 | write the new audit row |
| Update | TENANT#acme | COUNTER | ADD eventCount 1 |
若任一動作的條件失敗 — 比如說一個檢查該租戶未被停權的 ConditionCheck — 整筆請求就會以 TransactionCanceledException 取消,而且兩筆寫入都不會發生。記錄與計數器永遠不可能不一致。
每個動作上的 ConditionExpression 就是那根槓桿。若要斷言事件列尚不存在(讓重試無法產生重複)且租戶為使用中,你就在 Put 上組出 attribute_not_exists(SK)、並以 status = :active 作為一個 ConditionCheck 之類的條件。
在 DynamoDB 表達式建構器 中建構並複製這些帶類型的條件表達式,而不要手動拼湊 ExpressionAttributeNames 與 :val 佔位符 — 條件式寫入模式所產生的,正是 TransactWriteItems 想要的結構。
若要在不穩定的連線上安全重試,請附上一個用戶端 token:在 10 分鐘內以相同 token 重複的 TransactWriteItems 會回傳成功,而不會重新套用那些寫入
(冪等性)。
在 DynoTable 中實作
DynoTable 在底層就用交易來進行它自己的寫入:當你暫存好數筆項目編輯並提交時,它會以帶有樂觀鎖定條件表達式的 TransactWriteItems 送出,所以你那批編輯是全有或全無的 — 你絕不會半套地套用一次多項目變更。
這表示你可以在同一個暫存批次中編輯事件列與計數器、檢視差異,並原子式地一起提交兩者,而不必寫任何 SDK 程式碼。

陷阱與後續步驟
- 為雙倍容量編列預算。 一次交易式寫入計收的 WCU 是一般寫入的兩倍 — 對於偶爾、對一致性至關重要的成對寫入沒問題,但若把每一筆寫入都包進交易就所費不貲。請用在原子性確實重要之處。
- 明確處理
TransactionCanceledException。 它在條件失敗或與同一批項目上另一筆進行中的交易發生衝突時回傳。取消原因會告訴你是哪個動作失敗了 — 請檢視它們,不要盲目重試。 - stream 記錄不感知交易。 來自一筆交易的變更會逐步傳播到 Streams,並可能與其他變更交錯;消費端不能假設原子性或排序 — 請見 DynamoDB Streams。
- 不適合高輸送量的計數器。 單一熱計數器在大量並行交易負載下會被節流;若有此需要,請看原子計數器或將計數器分片。
交易是用於「這些寫入必須一致」的工具。一旦事件能一致地落地,下一項考量就是對它們做出反應 — 那就是 DynamoDB Streams。
下載 DynoTable 來暫存多項目編輯,並把它們作為單一交易對你自己的表格提交。


