以會變動(可變)的屬性對 DynamoDB 排序
你圍繞某個屬性建模 sort key,好讓你能依其順序查詢項目 — 然後該屬性變了。一張工單的 狀態、一筆訂單的狀態、一項任務的優先順序。這裡有 DynamoDB 丟給你的難題:你無法就地 更新鍵屬性。 主鍵在項目的整個生命週期中都是不可變的。改動屬於鍵一部分的值,你做的就 不是編輯項目 — 而是搬移它,而 DynamoDB 逼你明確地這麼做。
你可以更改 DynamoDB 的 sort key 嗎?
不行。sort key 是主鍵的一部分,而 DynamoDB 的鍵屬性是不可變的 — UpdateItem 無法編輯 partition key 或 sort key 的值,也沒有「搬移項目」這種操作。若要更改它,你必須刪除舊項目並寫入一個新的,或者改將會變動的值放在 GSI 的 sort key 上,讓它遠離基礎表格鍵。
- 鍵屬性是不可變的。 你無法對 partition key 或 sort key 的值執行
UpdateItem— DynamoDB 沒有「搬移項目」這種操作。 - 要更改鍵值,你得刪除舊項目並寫入一個新的 — 最好放在一個 交易裡,讓它具備原子性。
- 更好的做法:讓會變動的值遠離基礎表格鍵,改放到 GSI 的 sort key 上 — GSI 鍵可以變動,因為更新基礎項目只會 重新傳播索引項目。
- 在存取模式允許時,選擇不會變動的 sort key(時間戳記、不可變的 id)。
問題:你想用來排序的狀態,卻一直在變
假設你經營一個支援櫃台,想依狀態列出某團隊的工單,因此你把狀態放進 sort key:
PK: TEAM#7 SK: STATUS#open#TICKET#8842現在工單轉為 pending。你會想直接 UpdateItem 把 sort key 改成
STATUS#pending#TICKET#8842 — 但 DynamoDB 會拒絕任何更改鍵屬性的寫入。鍵就是項目
的地址;你無法就地編輯這個地址。你選來排序的狀態,正好就是那個坐不住的東西。
選項 1:刪除並重建(以原子方式)
如果該值必須存在於基礎表格鍵中,更改它就意味著移除舊項目並寫入新的:
1. DeleteItem PK=TEAM#7 SK=STATUS#open#TICKET#8842
2. PutItem PK=TEAM#7 SK=STATUS#pending#TICKET#8842 (same attributes)把它放進一個 TransactWriteItems,讓刪除與寫入要嘛
都成功、要嘛都失敗 — 否則兩者之間的當機會搞丟工單或讓它重複。這行得通,但每次狀態變更
現在都是兩次寫入外加一個交易;偶爾變更尚可接受,頻繁變更則代價高昂。
選項 2:讓會變動的值遠離基礎鍵(建議做法)
更乾淨的設計:讓基礎表格鍵是不可變的東西(工單 id),並把會變動、可排序的值放到 GSI 的 sort key 上。
Base: PK: TICKET#8842 status: "open" teamId: TEAM#7
GSI: GSI1PK: TEAM#7 GSI1SK: STATUS#open#TICKET#8842現在更改狀態只是對基礎項目的 status 屬性做一次普通的 UpdateItem — DynamoDB 允許
這麼做,因為 status 不是基礎表格的鍵。接著 DynamoDB 會自動重新傳播 GSI 項目到它
新的排序位置。一次寫入,沒有交易,不必跳那支刪除舞。
代價是:GSI 是最終一致,且會耗費額外的 儲存空間/寫入 — 但對於經常變動的值來說,這遠比每次變更都刪除並重建來得便宜。
在 DynoTable 中設計鍵
用 DynamoDB 表達式建構器為基礎讀取與 GSI 讀取 建構並預覽鍵條件。
在 DynoTable 中,你可以選擇查詢透過哪個 index 執行,並觀察會變動的值在 GSI 上排序, 而基礎項目保持其不可變的鍵 — 兩種讀取並排顯示在真實資料上。

陷阱與後續步驟
- 永遠別試圖對鍵屬性執行
UpdateItem— 它會被拒絕;鍵值在項目的生命週期中是固定的。 - 若你必須搬移它,就在一個交易裡做刪除+寫入 — 絕不要做成兩次無保護的寫入。
- 對於任何你既要排序又要變動的屬性,優先採用不可變的基礎鍵 + 一個 GSI。
- 別忘了 GSI 的最終一致 — 重新排序後的項目會在短暫的傳播延遲後才出現。
- 相關: sort key 策略、 GSI 與 LSI 的比較、交易。
想看看一個會變動的屬性在 GSI 上與在基礎表格上的排序有何不同嗎? 下載 DynoTable,直接探索你的 index。


