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.5

CountScannedCount 被求和了。ConsumedCapacity 没有——它是第一页的数字,而真正的总数是 284.5。botocore 的 DynamoDB 分页器配置把原因写得很明白:CountScannedCount 被列为结果键,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 要的是本页构建的那个表达式字符串。
  • 键条件是一个相等条件外加至多一个排序键比较=<<=>>=BETWEENbegins_with)。别的东西都放进 FilterExpression,boto3 会原样透传,而 DynamoDB 在读取之后才应用它。ScanIndexForward=False 会反转顺序,IndexName="..." 会改为指向某个索引。

用可视化的方式来做

DynamoDB 表达式构建器会把键条件和带类型的 ExpressionAttributeValues 映射写成 boto3 直接可用的 Python 代码——而当你敲成 {"N": 2010} 而不是 {"N": "2010"} 时,出问题的正是这一块。

想从一个键条件表单出发,把同样的查询指向自己的表,并在分页网格里读结果,就下载 DynoTable

相关指南

参考资料

可视化构建此请求

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

打开 DynamoDB 查询构建器

无需控制台即可使用 DynamoDB

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

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