Python(boto3)中的 DynamoDB Scan

boto3 里的一次 scan 是两个决定:请求本身,以及你怎么给它分页。下面的代码用的是内置分页器,它不是某人围着你的循环写的一层封装。它是五行 botocore 配置,而正是这五行决定了你的扫描是否正确、以及它花多少钱。(至于你到底该不该扫描,那是另一个问题。)

代码

import boto3

client = boto3.client("dynamodb")

paginator = client.get_paginator("scan")

items = []
for page in paginator.paginate(
    TableName="Music",
    FilterExpression="#filter0 >= :filterValue0",
    ExpressionAttributeNames={"#filter0": "Year"},
    ExpressionAttributeValues={":filterValue0": {"N": "2010"}},
):
    items.extend(page["Items"])

print(f"Matched {len(items)} items")

说明

  • 分页器是数据,不是代码。botocore 在 paginators-1.json 里给每个操作放了一条;Scan 那条是 {"input_token": "ExclusiveStartKey", "output_token": "LastEvaluatedKey", "limit_key": "Limit", "result_key": ["Items", "Count", "ScannedCount"], "non_aggregate_keys": ["ConsumedCapacity"]}。下面的一切都是从这些键里推出来的。
  • PaginationConfig={"PageSize": n} 设置的是 Limit,因为 Limit 就是那个 limit_keyLimit 限制的是 读取 的项数,从来不是返回的项数,所以配上 FilterExpression 时,一页可以是空的,却照样有成本。
  • MaxItems 数的是 result_key 里的项,并交给你一个 NextToken,你可以在之后的进程里把它作为 StartingToken 传回去。它并不会阻止请求读过你设的那条线。
  • build_full_result() 只聚合 result_key 字段ItemsCountScannedCount 会被累加;ConsumedCapacitynon_aggregate_key,所以合并后的结果会把一页的容量当成整次扫描的容量报出来。请自己逐页累加,否则你会少报整整一个页数的倍数。
  • FilterExpression 是在读取之后于服务端运行的,所以你是按 ScannedCount 计费的,不是 Count#filter0Year 做别名,是因为它是保留字;没有这个别名,请求在读到任何东西之前就失败了。
  • 错误全都以 botocore.exceptions.ClientError 的形式到达。按 e.response["Error"]["Code"] 分支;每个错误对应的类只作为客户端上生成的属性存在(client.exceptions.ProvisionedThroughputExceededException),从来不是可导入的符号。
  • 资源 API 是另一套手感Table.scan 接收原生 Python 类型,把数字作为 decimal.Decimal 返回,并用 Attr("Year").gte(2010) 而不是占位符映射来构建过滤条件。

一个被过滤的页面实际花多少钱

60 个项,每个大约 2 KB,Year = 2024 命中其中两个,PageSize=10,针对 DynamoDB Local 运行:

page 1: Count=0 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 2: Count=1 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 3: Count=0 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 4: Count=1 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 5: Count=0 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 6: Count=0 ScannedCount=10 CU=2.5 LastEvaluatedKey=yes
page 7: Count=0 ScannedCount=0 CU=0.0 LastEvaluatedKey=no
total CU across pages: 15.0

六个真实页面里有四个什么都没返回,而且是全价。这正是分页器要防的那种 bug 的形状:一个在 Items 为空时就跳出的手写循环会在第 1 页退出,把两首命中的歌报告成零。

第 7 页是另一半。第 6 页在表里最后一个项上撞到了它的 Limit,所以 DynamoDB 照样返回了一个 LastEvaluatedKey,分页器又花了一次往返才知道后面已经没东西了。一个 LastEvaluatedKey 的意思是"我停下了",不是"还有更多"。

对同一次扫描调用 build_full_result() 报告的是 CapacityUnits: 2.5。那六页实际消耗了 15.0。

不用自己写循环也能分页

DynamoDB 查询构建器会把过滤条件、别名映射和分页循环拼成一个可运行的程序,于是上面那个 LimitCount 的陷阱在你粘贴之前就已经处理好了。要以交互方式而不是从脚本里翻一张真实的表,请下载 DynoTable

相关指南

参考资料

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

可视化构建此请求

在免费的 DynamoDB 查询构建器中组装此操作 —— 键条件、筛选、索引、Limit、排序方向和分页循环 —— 再把它作为可运行的 SDK v3、CLI 或 boto3 程序复制回来。

打开 DynamoDB 查询构建器

无需控制台即可使用 DynamoDB

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

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