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() 只是往一个列表里追加。在缓冲区攒够 25 条或 with 块退出之前,什么都不会被校验、序列化或发送,所以坏项目的堆栈跟踪来自刷新,而不是来自提供它的那次 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,所以对多数导入脚本来说这是第一次运行就会遇到的失败。给 json.loadsparse_float=Decimal 可以从源头解决它。

如果你在手工地把原生 Python 值和线路格式来回转换,DynamoDB JSON 转换器会把同一个项目的两侧同时显示出来,好让你看清你的 Decimal 实际变成了什么。

要从 CSV 或 JSON 批量导入而不必自己写类型映射,请下载 DynoTable

相关示例

参考资料

最后核实于 2026-07-28,依据上方链接的 AWS 官方文档。

无需控制台即可使用 DynamoDB

一款快速的 DynamoDB 桌面客户端,可运行 DynamoDB 无法执行的真正 SQL——JOINs、GROUP BY、聚合——并支持可视化编辑和运行在你自己的 Bedrock 密钥上的 AI agent。

30 天免费试用,无需信用卡 — 之后为无时间限制的免费版。