iT邦幫忙

2025 iThome 鐵人賽

DAY 3
0

「主鍵在 DynamoDB 中扮演靈魂的角色,這個我們在昨天已經說過」諾斯克大師重申,「這是因為在 key-value 型態的資料庫中,唯有先取得 key 才有辦法做下一步的動作。」

洛基看著精神奕奕的大師,分心了一下想到自己到他那個年紀不知道是否還能有這麼充沛的精神與體力。

出身在軍人世家,從小就夢想在戰場上建立戰功,有朝一日可以當上將軍,一如他的父親一樣。沒想到在戰場上受了傷,只能退到後勤單位,這個夢想看是無緣了。

程式開發一直是他的興趣,寫寫小程式,參與一些開源專案,當成是一種腦力遊戲,沒想過拿來賺錢或是當成職業,但這個興趣卻意外地在他傷後帶他走上專業開發的工作,而且還帶他來到這麼遠的地球,人生的際遇怎麼有辦法規劃呢?

「你的靈魂好像不在這裡呢,上尉?」

洛基猛然站起行起軍禮,「報告長官,對不起我分心了。」

「別緊張,上尉,這裡不是部隊,我也不是你的長官,放輕鬆一點,再喝點茶吧。」

洛基緊忙啜了一口茶。

大師想了一下,「不如我們先來動動手吧。上尉,麻煩你查一下 DynamoDB 的說明。」

洛基緊張地回問:「查 DynamoDB 的說明?」

這時 Hippo 接話:「在命令列中輸入 aws dynamodb help 就可以了,菜鳥!」

大師笑著說:「Hippo 我記得我沒有把你的幽默模式打開呀」

Hippo 回說:「菜鳥偵測器檢測到濃濃菜味,需要緊急處理。」

大師和洛基相視大笑。

洛基精神一振,依 Hippo 所說,輸入了 aws dynamodb help 指令,畫面出現了 DynamoDB 的基本介紹和一堆指令的清單。

「你記得這兩天我們用過哪些指令嗎?」大師問。

「我們建立了資料表,所以用了 create-table,有新增和取得資料,所以用了 put-itemget-item,沒記錯的我們有用上這些。」

「很好,接下來我要麻煩你看一下,所有指令中,後綴中有 -item 的指令還有哪些?」

洛基很快地掃讀後回覆有batch-get-itembatch-write-itemdelete-itemget-itemput-itemupdate-item 6個。

大師說:「這兩天我們會專注在這幾個指令,不過在那之前,我們要先理解一下為什麼這些指令為什麼會強調 -item

item based 的觀念

「在 DynamoDB 中,一筆資料或說一筆記錄,我們稱為一個 item。所以剛剛說的指令,先不談兩個 batch 行為的話,剩下 4 個,就是對 item 的操作。」

「看起來像是標準的 CRUD 對應:Create、Read、Update、Delete?」洛基推測。

「表面上看是這樣,但這裡有個重要的差異。」大師走到白板前寫下:

get-item    → 讀取 item
delete-item → 刪除 item  
update-item → 部分更新 item
put-item    → 寫入 item(新增或完整替換)

「注意到了嗎?put-item 不只是 Create,它是『寫入』——如果 item 不存在就新增,如果存在就完整替換。這是很多人踩坑的地方。」

洛基若有所思:「所以 put-item 既可以新增也可以更新?」

「正確,但要小心的是,它的『更新』是完整替換,不是部分更新。」大師強調,「DynamoDB 是 key-value 型態的資料庫,這些 item 操作都有個共同特徵——你必須明確指定主鍵。」

「大概可以理解,不過我沒有很清楚和其他資料庫的差異,原本在處理資料時,不是也都是一筆一筆處理嗎?像是我要更新一位報名者的研討會,不是就得拿出這個報名者的記錄,去改寫它的研討會代碼嗎?」

大師反問:「如果有個 A 研討會取消,所有報名者通通改成 B 研討會,那你使用 SQL 會怎麼做?」

「先假設這個資訊記錄在 registrations 資料表中,我會用 UPDATE registrations SET event = 'B' WHERE event = 'A' 的方式來更新它。」

「在 DynamoDB 的世界裡,我們不這樣思考。」大師接著說:「item 操作的核心精神是——每個操作都需要明確的主鍵。」

洛基有點困惑:「但剛剛不是還有提到兩個 batch 操作嗎?」

「好問題。batch-write-item 確實可以一次處理多個 item,但它不是『更新所有符合條件的資料』,而是『這裡有 25 個明確的 item,請幫我一次處理』。」大師解釋,「DynamoDB 沒有條件式批次更新——你不能說『把所有 event 是 A 的改成 B』。」

「具體來說,batch-write-item 要怎麼運作?」洛基追問。

「你必須先找出每個受影響 item 的主鍵,然後提供給 batch-write-item 批次處理,而且一次最多只能處理25個。」

「這樣...會不會...太...」洛基驚訝地不知道該選用哪個字來表達。

「對錯利弊往往都是看你站在什麼立場上。像現在你就是站在過去的 SQL 思維來作價值判斷。但就 DynamoDB 的立場來說,這聽起來像是限制,但這正是 DynamoDB 能提供穩定效能的關鍵——每個 item 操作都是獨立且可預測的。而這就是它高速、能夠承載大量資料的原因和方法。」大師微笑地說。

洛基沉思了一下,大師說的沒錯,不過由於和自己的認知差距很大,一時之間也很難消化。

「我知道現階段要你完全轉換思維並不容易,所以就先記住這個原則:在 DynamoDB 的 item 操作中,主鍵是一切的起點。」大師接著說:「不過今天的驚奇之旅才剛要開始。」

put 的覆蓋行為

「既然我們知道 put-item 可以新增也可以更新,讓我們來看看它『更新』時會發生什麼事。」大師說,「請你先建立一筆完整的活動資料。」

Hippo 這時接話說:「之前我們的資料比較簡單,直接在命令列寫入資料,為了方便加入複雜資料,我們可以先把資料寫成 JSON 格式後,再使用 aws 指令指行匯入,詳情可以用 help 查詢」

同時 Hippo 也提供了如下的測試資料給洛基:

欄位 型態
PK 字串 EVENT#MARS-2024
name 字串 火星防禦研討會
date 字串 2024-06-15
capacity 數字 500
registered 數字 0
location 字串 火星第七區
speaker 字串 戰神將軍
topic 字串 星際防禦新策略
duration 數字 180
status 字串 OPEN

洛基在查詢指令後先建立一個檔名為 complete-event.json 的 JSON 檔案:

{
  "PK": {"S": "EVENT#MARS-2024"},
  "name": {"S": "火星防禦研討會"},
  "date": {"S": "2024-06-15"},
  "capacity": {"N": "500"},
  "registered": {"N": "0"},
  "location": {"S": "火星第七區"},
  "speaker": {"S": "戰神將軍"},
  "topic": {"S": "星際防禦新策略"},
  "duration": {"N": "180"},
  "status": {"S": "OPEN"}
}

Hippo 說「很好,現在用 put-item 指令將它存入資料庫。」

洛基寫下了以下指令來執行:

aws dynamodb put-item \
  --table-name IntergalacticEvents \
  --item file://complete-event.json \
  --endpoint-url http://localhost:8000

資料匯入後,洛基也用 get-item 確認有成功寫入。這時大師給了新的情境:「現在假設有人報名了,你想更新報名人數。試試看只用 put-item 更新 registered 欄位。」

洛基想了想,建立了一個新的 update-registered.json JSON 檔案:

{
  "PK": {"S": "EVENT#MARS-2024"},
  "registered": {"N": "1"}
}

一樣順利地匯入了。「現在查詢看看這筆資料。」大師說。

洛基看著輸出結果,驚訝地說:「其他所有欄位都消失了!只剩下 PK 和 registered!」

「這就像重新粉刷整面牆。」大師接著說:「Put 操作不是『更新』,而是『完整替換』。不管原本的 item 有什麼屬性,全部都會被新的 item 取代。這是 DynamoDB 的簡單哲學——沒有部分更新的複雜性,只有完整替換的確定性。」

洛基若有所思:「所以如果我只想改一個欄位,卻用 put-item,就會把其他資料都弄丟?」

「正是如此。這就是我剛才說 put-item 容易踩坑的原因。」大師說,「因為 put-item 可以用來更新,很多人以為 put-item 像 SQL 的 UPDATE,只更新指定的欄位。但實際上,put-item 的邏輯是:『這是一個完整的 item,請用它取代該主鍵位置上的任何東西』——不管原本有什麼。」

「那這不是很危險嗎?」洛基擔心地問。

「危險來自於不理解。一旦你理解了,就能正確使用它。」

一個危險的想法

「那如果我真的需要保留原本的資料呢?」洛基問,「是否可以先取出資料完整資料,修改完後,再用 put-item 更新放回去?」

「這個一般稱之為 Get-Modify-Put 模式:先讀取、修改、再寫回。讓我們實際試試看,你就會發現問題。」大師說,「 Hippot 請模擬兩個人同時報名的情況。」

Hippo 開始操作,User A 先讀取當前的活動資料,在本地修改 registered 從 0 變成 1,準備寫回。同時 User B 也做了同樣的事。

Hippo 在白板上顯示結果--

https://ithelp.ithome.com.tw/upload/images/20250917/20178813KFfd9jNBj1.jpg

洛基恍然大悟:「第二個人的更新會覆蓋第一個人的!我們掉了一個報名。」

「不只如此。」大師補充,「這個模式還有其他問題:」

  1. 效能差:需要兩次網路往返(讀+寫)
  2. 成本高:消耗額外的 RCU 和 WCU
  3. 複雜度高:需要處理重試邏輯和版本衝突
  4. 非原子性:讀和寫之間不是原子操作

「那什麼時候適合用 put-item?」洛基問。

Put-Item 的正確場景

「Put-item 的完整替換特性不是缺陷,而是為特定場景設計的。」大師解釋:

「第一種:建立全新的 item」

# 確保不會意外覆蓋已存在的活動
aws dynamodb put-item \
  --table-name IntergalacticEvents \
  --item file://new-event.json \
  --condition-expression "attribute_not_exists(PK)" \
  --endpoint-url http://localhost:8000

「第二種:需要完整替換的場景」

大師舉例:「比如活動狀態變更,從草稿變成發布狀態,整個資料結構都會改變:」

// 草稿狀態
{
  "PK": {"S": "EVENT#DRAFT-001"},
  "status": {"S": "DRAFT"},
  "lastModified": {"S": "2024-01-10"},
  "author": {"S": "洛基"}
}

// 發布後完全不同的結構
{
  "PK": {"S": "EVENT#MARS-2024"},
  "status": {"S": "PUBLISHED"},
  "name": {"S": "火星防禦研討會"},
  "date": {"S": "2024-06-15"},
  "capacity": {"N": "500"},
  "registered": {"N": "0"},
  "location": {"S": "火星第七區"}
}

洛基若有所思:「所以 put-item 適合『不需要考慮原有資料』的場景。但如果需要部分更新...」

「那就不該用 put-item。」大師肯定地說,「這就是為什麼 DynamoDB 提供了 update-item。」

條件寫入的防護

「既然 put-item 會完整替換,我們至少要防止意外覆蓋重要資料。」諾斯克說,「DynamoDB 提供了條件表達式作為安全防護。」

「最常見的防護是確保不會覆蓋已存在的資料:」

# 只有當 PK 不存在時才寫入(適合建立新資料)
aws dynamodb put-item \
  --table-name IntergalacticEvents \
  --item file://new-event.json \
  --condition-expression "attribute_not_exists(PK)" \
  --endpoint-url http://localhost:8000

「如果活動已存在,這個操作會失敗,保護你不會意外覆蓋。」大師解釋。

「相反的,如果你確定要替換已存在的資料:」

# 只有當 PK 存在時才寫入(確保是替換而非新增)
aws dynamodb put-item \
  --table-name IntergalacticEvents \
  --item file://replacement-event.json \
  --condition-expression "attribute_exists(PK)" \
  --endpoint-url http://localhost:8000

洛基提出疑問:「但如果我需要更複雜的條件呢?比如只在特定狀態下才能替換?」

「條件表達式很強大。」大師在白板上寫下範例:

# 只有當活動狀態是 DRAFT 時才能替換
aws dynamodb put-item \
  --table-name IntergalacticEvents \
  --item file://published-event.json \
  --condition-expression "attribute_exists(PK) AND #status = :draft" \
  --expression-attribute-names '{"#status": "status"}' \
  --expression-attribute-values '{":draft": {"S": "DRAFT"}}' \
  --endpoint-url http://localhost:8000

「這確保了狀態轉換的正確性。」大師說,「但記住,這些都是基於『完整替換』的前提。」

洛基突然想到:「如果我只是想增加報名人數,用 put-item 就必須:讀取整個 item、在應用層計算新值、再寫回整個 item。這不只麻煩,還有併發問題...」

「完全正確!」大師讚許地點頭,「你已經理解了 put-item 的限制。對於這種需要部分更新、原子操作的場景...」

「需要另一個工具。」Hippo 接話。

「沒錯。」大師總結,「Put-item 有它的價值——建立新資料、完整替換。但當你需要部分更新、原子計數、併發安全的修改時,就是 update-item 登場的時候了。」

「所以 put 和 update 不是替代關係,而是互補的?」洛基問。

「正是如此。選擇正確的工具解決正確的問題,這是資深開發者的智慧。」大師微笑,「明天我們會深入 update-item,你會看到它如何優雅地解決今天遇到的這些問題。」

Hippo 的課外教學

條件表達式完整語法

Hippo:「菜鳥,讓我教你條件表達式的各種用法!」

  1. 檢查屬性存在性

    • attribute_exists(屬性名):屬性必須存在
    • attribute_not_exists(屬性名):屬性不能存在
  2. 比較運算子

    • =, <>, <, <=, >, >=:基本比較
    • BETWEEN x AND y:範圍檢查
    • IN (val1, val2, ...):值列表檢查
  3. 邏輯運算子

    • AND:所有條件都要滿足
    • OR:至少一個條件滿足
    • NOT:條件取反
  4. 函數

    • begins_with(屬性, 前綴):字串開頭檢查
    • contains(屬性, 子字串):包含檢查
    • size(屬性):檢查集合或字串大小

ReturnValues 參數

當你執行 put-item 時,可以選擇返回什麼:

# 返回舊值(被替換掉的 item)
--return-values ALL_OLD

# 不返回任何值(預設,最高效)
--return-values NONE

# 注意:PutItem 不支援 ALL_NEW
# 因為新值就是你傳入的值,不需要返回

上一篇
Day 2: 主鍵是靈魂
下一篇
Day 4:Update 操作的藝術
系列文
DynamoDB銀河傳說首部曲-打造宇宙都打不倒的高效服務6
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言