Python 中的 DynamoDB 批次寫入(boto3 batch_writer)

batch_writer() 是唯一一個 Python 比其他 SDK 更省事的 DynamoDB 呼叫。它會緩衝 put 與 delete、把它們切成每批 25 筆的 BatchWriteItem 請求,並自行重送未處理的項目。它沒有替你擋掉的,是那兩個毀掉大多數大量載入的失敗,而且兩者都在清空緩衝時才浮現,而不是在提供那筆壞項目的那一行。

程式碼

import boto3

dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("Music")

songs = [
    {"Artist": "Arturo Sandoval", "SongTitle": "Cubano Chant", "AlbumTitle": "Danzon", "Year": 1994},
    {"Artist": "Arturo Sandoval", "SongTitle": "A Mis Abuelos", "AlbumTitle": "Danzon", "Year": 1994},
    {"Artist": "Arturo Sandoval", "SongTitle": "Groovin' High", "AlbumTitle": "Swingin'", "Year": 1996},
]

with table.batch_writer() as batch:
    for song in songs:
        batch.put_item(Item=song)
    # batch_writer buffers deletes too — target a key you're NOT also putting
    # (two writes to the same key in one batch are rejected as a duplicate)
    batch.delete_item(Key={"Artist": "Ella Fitzgerald", "SongTitle": "Misty"})

print(f"Buffered {len(songs)} puts + 1 delete; the batch flushes on exit")

說明

  • 延後清空batch.put_item() 只是把項目附加到一個 list。在緩衝區累積到 25 筆、或 with 區塊結束之前,什麼都不會被驗證、序列化或送出,所以壞項目的 traceback 來自清空緩衝的那一刻,而不是提供它的那次 put_item 呼叫。如果你是從迭代器載入,請自己記錄有哪些東西進了緩衝區。
  • 純 Python 值 — 這是 resource API,所以你寫的是 1994,不是 {"N": "1994"}。任何帶小數的值都必須用 Decimal;float 會被收進緩衝區,然後在清空時被拒絕。
  • batch_writer()Table 的方法。讀取端的對應物則不是:batch_get_item 掛在 ServiceResource 上,table.batch_get_item 並不存在。批次讀取完全沒有緩衝、切批或重試的輔助工具。
  • UnprocessedItems,而不是錯誤 — 它只處理這一種重試。被節流的寫入會重送;ValidationException 則會往外拋。改走 client.batch_write_item 就得自己寫整個迴圈,就像 Node.js 範例那樣。
  • 它無法解除服務限制。每個請求 25 筆寫入、每筆項目 400 KB、每個請求 16 MB,不支援條件也不支援更新,而且每次 put 都會整筆取代已存的項目。需要保護條件,或要全有全無?請用 TransactWriteItems

batch_writer 在清空緩衝時實際做了什麼

緩衝 30 筆 put,看看它實際發出哪些呼叫。包裝 table.meta.client.batch_write_item 並記錄請求大小,對照 DynamoDB Local 3.3.0:

batch sizes sent: [25, 5]

兩個請求,切在服務限制上,剩下的由 __exit__ 清出。那次清空是無條件的:在區塊內拋出 RuntimeError,緩衝中的項目照樣會在離開時被寫入。中途死掉的大量載入留下的是寫了一半的資料,而不是乾淨的狀態。

現在來看那兩個失敗。把同一個鍵緩衝兩次,這正是你的來源資料一出現重複就會發生的事:

with table.batch_writer() as batch:
    batch.put_item(Item={"Artist": "Dup", "SongTitle": "Key", "Year": 1})
    batch.put_item(Item={"Artist": "Dup", "SongTitle": "Key", "Year": 2})
botocore.exceptions.ClientError: An error occurred (ValidationException) when calling the
BatchWriteItem operation: Provided list of item keys contains duplicates

兩次 put_item 都沒有抱怨。batch_writer() 不會自動去重,除非你明講,而明講的方式是 table.batch_writer(overwrite_by_pkeys=["Artist", "SongTitle"])。把同樣那兩筆 put 交給它,項目最後會存成 Year: 2 — 緩衝區只保留每個鍵的最後一次寫入,所以如果你那兩列本來該是不同的項目、只是鍵設錯了,這個去重就是無聲的資料遺失。

第二個失敗完全是 boto3 自己的,根本到不了 DynamoDB:

TypeError: Float types are not supported. Use Decimal types instead.

一個值為 4.5Rating 會安安靜靜地待在緩衝區裡,然後在清空時爆炸。Decimal("4.5") 則能正確往返成 {"N": "4.5"}。用 json.loads 從 JSON 讀出價格或評分時,每個數字都是 float,所以這對大多數匯入腳本來說是第一次執行就會踩到的失敗。把 parse_float=Decimal 傳給 json.loads 就能從源頭修掉它。

如果你是手動在原生 Python 值與傳輸格式之間搬移,DynamoDB JSON 轉換器會同時顯示同一筆項目的兩種樣貌,讓你看到你的 Decimal 實際變成了什麼。

想從 CSV 或 JSON 大量載入、又不想自己寫型別對應,請下載 DynoTable

相關範例

參考資料

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

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

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

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