中階閱讀時間 1 分鐘

DynamoDB ReturnValues:從一次寫入取得舊的或新的項目

DynamoDB 寫入預設只傳回成功與否,別無其他。但你往往需要寫入前後的資料 — 更改之前的值, 或更改之後的最新值。最直覺的修法是再來一次 GetItem,這既是一次額外的往返,也是一個 競態:別人可以在中間寫入。DynamoDB 用 ReturnValues 參數同時避開這兩者,它會把舊的或 新的項目作為寫入本身的一部分以原子方式交回給你。

DynamoDB 的 ReturnValues 有什麼作用?

ReturnValues 告訴 DynamoDB 寫入操作在同一次呼叫中交回項目,讓你省去第二次 GetItem 及其所造成的競態。PutItemDeleteItem 接受 NONEALL_OLDUpdateItem 接受全部五個(NONEALL_OLDUPDATED_OLDALL_NEWUPDATED_NEW),以原子方式傳回新的或舊的值。

  • ReturnValues 會把項目作為寫入的一部分傳回 — 沒有第二次讀取、沒有競態。
  • NONE(預設) — 不傳回任何東西。
  • ALL_OLD — 寫入之前整個項目原本的樣子。
  • UPDATED_OLD — 只有此次更新所更改的屬性,更改前的值。
  • ALL_NEW — 寫入之後的整個項目。
  • UPDATED_NEW — 只有更改過的屬性,更改後的值。
  • PutItem/DeleteItem 只接受 NONEALL_OLD UpdateItem 五個都接受。

問題:你需要剛剛覆寫掉的那個值

假設你經營一個支援櫃台,一位專員把一張工單的狀態從 open 改為 pending。你的稽核記錄 需要記下更改前的狀態是什麼。沒有 ReturnValues 的話你得:

  1. GetItem 讀取目前的狀態,
  2. UpdateItem 設定新的狀態。

在步驟 1 與 2 之間,另一位專員可能更改了狀態 — 現在你的稽核記錄記下的是一個過時的「更改前」 值。更糟的是,這是為了一個邏輯操作而做的兩次呼叫。ReturnValues 把它收攏成一次原子的 UpdateItem,傳回的舊狀態正是寫入當下真正的樣子。

這五個選項,以及各自何時使用

UpdateItem 支援整套;要選的是你需要項目的哪一部分以及寫入的哪一側

ReturnValues傳回何時使用
NONE你不需要拿回項目(預設)
ALL_OLD整個項目,寫入前稽核 / 「我剛剛取代掉的是什麼?」
UPDATED_OLD更改過的屬性,寫入前你只在意你動過的欄位
ALL_NEW整個項目,寫入後你需要最新的完整項目以回傳給呼叫方
UPDATED_NEW更改過的屬性,寫入後讀回你剛剛遞增的計數器/值

UPDATED_NEW 是日常英雄:用一個 update expression 遞增一個計數器,並在同一次呼叫中 讀回新的總計,沒有競態。對於支援工單的稽核,ALL_OLD(若你只記錄狀態欄位則為 UPDATED_OLD)能以原子方式擷取更改前的狀態。

注意這個不對稱:PutItemDeleteItem 只支援 NONEALL_OLD — 刪除沒有「新」 值可傳回,而一個 put 的新值就是你送出去的東西。只有就地變更的 UpdateItem 才提供全部五個。 AWS 文件記載了 確切的對照表。

在 DynoTable 中撰寫更新

DynamoDB 表達式建構器以視覺化方式組裝 UpdateItem 及其 update expression — 它會產生 SET/ADD 子句加上屬性名稱與值的映射。在應用程式中, DynoTable 會在一筆暫存的寫入提交後顯示產生的項目,因此你能直接看到新的狀態。

在 DynoTable 中檢視項目的暫存變更 — 提交更新前的舊值與新值。
在 DynoTable 中檢視項目的暫存變更 — 提交更新前的舊值與新值。

陷阱與後續步驟

  • 別用 GetItem 然後寫入來讀取一次更改前後的值 — 那是一次往返加一個競態; 請使用 ReturnValues
  • UPDATED_* 只傳回動過的屬性 — 若你需要整個項目,請用 ALL_*
  • PutItem/DeleteItem 無法傳回新值 — 只有 NONE/ALL_OLD
  • ReturnValues 不是 condition 的替代品 — 要保護一次寫入,請加上 condition expression;要讀回它的效果, 請用 ReturnValues。兩者可以組合使用。
  • 相關: update expressions原子計數器

想在不寫腳本做兩次呼叫的情況下進行編輯並看到前後對照嗎? 下載 DynoTable,直接編輯你的項目。

已更新