Python 中的 DynamoDB Query(boto3)
本页之所以短,是因为 boto3 的 query 分页器把 LastEvaluatedKey 彻底藏起来了。它同时也藏起了一个你八成想要的数字,而这才是你信任它之前值得知道的部分。至于到底什么时候该用 query,参见 Query vs. Scan。
代码
import boto3
client = boto3.client("dynamodb")
paginator = client.get_paginator("query")
items = []
for page in paginator.paginate(
TableName="Music",
KeyConditionExpression="#hashKey = :hashKeyValue AND begins_with(#rangeKey, :rangeKeyValue)",
ExpressionAttributeNames={"#hashKey": "Artist", "#rangeKey": "SongTitle"},
ExpressionAttributeValues={":hashKeyValue": {"S": "Arturo Sandoval"}, ":rangeKeyValue": {"S": "C"}},
):
items.extend(page["Items"])
print(f"Found {len(items)} items")分页器不会替你把账单加起来
针对一份 600 首歌的固定数据(每首约 3.9 KB,全部在 Artist = "Arturo Sandoval" 之下),这个循环产出三页:271、271 和 58 个项目,分别花掉 128.5、128.5 和 27.5 个读取单元。让同一个分页器给出一个合并后的结果,你会拿到这个:
build_full_result() -> Items 600 Count 600 ScannedCount 600
ConsumedCapacity.CapacityUnits 128.5Count 和 ScannedCount 被求和了。ConsumedCapacity 没有——它是第一页的数字,而真正的总数是 284.5。botocore 的 DynamoDB 分页器配置把原因写得很明白:Count 和 ScannedCount 被列为结果键,ConsumedCapacity 被列为非聚合键。如果你是从 build_full_result() 记录容量的,那你把一次整分区读取少报了一半还多。
上面 for page in paginator.paginate(...) 循环里那些逐页的字典是原始响应,所以自己把 page["ConsumedCapacity"]["CapacityUnits"] 加起来,得到的就是诚实的 284.5。
那个让你多跑 58 次往返的 Limit
Limit 是一个合法的 query 参数,所以 paginate() 会接受它,而它并不是 Python 用户以为的那个参数:
paginate(..., Limit=10) -> 61 pages, 10 items each
paginate(...) -> 3 pages它限制的是每次请求的项目数,而不是总数,于是分页器兢兢业业地发了 61 次 HTTP 调用去取同样这 600 个项目。要限制总数,用 PaginationConfig={"MaxItems": 10};映射到 Limit 的那个旋钮是 PaginationConfig["PageSize"]。
2026-07-28 针对 9000 端口上的 DynamoDB Local(amazon/dynamodb-local),使用 CPython 3.14.6 上的 boto3 1.43.58 实测。
说明
- 这个 client 在两个方向上说的都是 DynamoDB JSON。值以
{"S": "Arturo Sandoval"}的形式进去,Year以{"N": "1994"}的形式回来。而资源API(boto3.resource("dynamodb").Table(...).query)会双向转换,交给你Decimal('1994')——这对金额来说是对的,但第一次看到它拒绝和一个float相加时会很意外。 Key("Artist").eq(...)只属于资源 API。把它传给 client,请求还没发出去就会抛错:ParamValidationError: Invalid type for parameter KeyConditionExpression ... valid types: <class 'str'>。client 要的是本页构建的那个表达式字符串。- 键条件是一个相等条件外加至多一个排序键比较(
=、<、<=、>、>=、BETWEEN、begins_with)。别的东西都放进FilterExpression,boto3 会原样透传,而 DynamoDB 在读取之后才应用它。ScanIndexForward=False会反转顺序,IndexName="..."会改为指向某个索引。
用可视化的方式来做
DynamoDB 表达式构建器会把键条件和带类型的 ExpressionAttributeValues 映射写成 boto3 直接可用的 Python 代码——而当你敲成 {"N": 2010} 而不是 {"N": "2010"} 时,出问题的正是这一块。
想从一个键条件表单出发,把同样的查询指向自己的表,并在分页网格里读结果,就下载 DynoTable。
相关指南
- Query vs. Scan——为什么
query才是合理的默认选择。 - 键条件表达式——每一个合法的分区键/排序键运算符。
- "Query condition missed key schema element"——键条件点错了属性名,或者干脆跳过了分区键。
- "Query key condition not supported"——键条件用不了的运算符,比如 contains 或者第二个排序键条件。