用 AWS CLI 做 DynamoDB Query
aws dynamodb query 读取一个分区,可选地用排序键收窄范围(Query vs Scan 讲了什么时候该这么选,键条件表达式列出了每一个合法运算符)。CLI 在此之上叠加的是它自己的一层分页,而这正是这条命令上大多数意外的来源。
代码
aws dynamodb query \
--table-name 'Music' \
--key-condition-expression '#hashKey = :hashKeyValue AND begins_with(#rangeKey, :rangeKeyValue)' \
--expression-attribute-names '{"#hashKey":"Artist","#rangeKey":"SongTitle"}' \
--expression-attribute-values '{":hashKeyValue":{"S":"Arturo Sandoval"},":rangeKeyValue":{"S":"C"}}'#hashKey/#rangeKey 这两个别名通过 --expression-attribute-names 解析到 Artist/SongTitle,正是它们让保留字不至于弄坏这条命令。加上 --no-scan-index-forward 可得到降序的排序键顺序;默认是升序。
分页
默认情况下 CLI 会自动分页——它在内部跟随 LastEvaluatedKey 并打印合并后的结果。要手动翻页(例如面对很大的结果集),用这些参数来控制:
aws dynamodb query \
--table-name 'Music' \
--key-condition-expression '#hashKey = :hashKeyValue' \
--expression-attribute-names '{"#hashKey":"Artist"}' \
--expression-attribute-values '{":hashKeyValue":{"S":"Arturo Sandoval"}}' \
--page-size 100 \
--max-items 50
# The output includes a "NextToken"; pass it back with --starting-token to continue.说明
CLI 把分页藏了起来,连成本数字也一起藏了。往一个分区里灌入 30 个各约 60 KB 的项目,合计约 1.8 MB,因此是两个服务端页;然后带上 --return-consumed-capacity TOTAL 用三种方式跑同一个查询:
default (auto-paginate) Count: 30 CapacityUnits: 132.0 LastEvaluatedKey: null
--no-paginate Count: 18 CapacityUnits: 132.0 LastEvaluatedKey: {…S017}
--max-items 3 Count: 18 items printed: 3 NextToken: eyJFeGNsdXNpdmVTdGFydEtleSI6…手动翻页才露出真实成本:第 1 页 18 个项目、132.0 个单元,第 2 页 12 个项目、88.0 个单元,所以这个查询实际消耗了 220.0 个读取单元。自动分页那次发出了两个调用、返回了全部 30 个项目,却报告 132.0。CLI 会跨页合并 Items 和 Count,但不合并 ConsumedCapacity,所以打印出来的数字把这个查询低估了 40%。如果你要根据 CLI 输出来规划容量,请手动翻页,否则你规划的只是一页的量。
--max-items 是打印上限,它不是 Limit。上面第三次运行打印了三个项目,却仍然报告 Count: 18 和 ScannedCount: 18,因为它截断的那个服务端页有 18 个项目、大约 1 MB。这些你全都付了钱。真正能限制读取量的 DynamoDB 参数叫 Limit,而 CLI 把它暴露为 --page-size。
所以这两个参数干的是互不相关的活。--page-size 会变成 API 的 Limit,改变每次服务调用读多少;--max-items 只决定合并后的结果有多少会到达你的终端,并为剩下的部分吐出一个 NextToken。那个 token 是 CLI 自己记账用的 base64 数据块,不是 DynamoDB 的 LastEvaluatedKey,它要通过 --starting-token 传回去。
没有 --limit,也没有 --exclusive-start-key。在 2.36.9 上执行 aws dynamodb query help,摘要里两个都不出现:CLI 移除了 DynamoDB 的两个分页参数,换上了自己的三个。所以那个最自然的循环——从一次调用里取出 LastEvaluatedKey 再喂给下一次——根本没有参数可喂。回到原始 API 的路子是 --cli-input-json,它原封不动地接收整个请求:
--cli-input-json with "Limit": 5 and an "ExclusiveStartKey"
→ Count: 5 CapacityUnits: 37.0 LastEvaluatedKey: {"Artist":…,"SongTitle":"S007"}注意这同时也把分页器关掉了:即便没有 --no-paginate,这次运行也只返回了一页和一个真正的 LastEvaluatedKey。如果你要为一个大分区写 shell 循环,--cli-input-json 是诚实的形式,而 --no-paginate 是快捷的形式。
--query 是在钱花完之后才跑的。全局的 --query 参数是在你的 shell 里对响应应用 JMESPath。像 Items[?Year > '2010'] 这样的 JMESPath 表达式看起来像过滤器,其实不是:在 JMESPath 看到它们之前,每一个项目都已经被读取、传输并计费了。--filter-expression 至少能阻止数据被传输,但 AWS 明确写着它「是在项目已经被读取之后才应用的;过滤这个过程不会消耗任何额外的读容量单元」(抓取于 2026-07-28)。这话是双刃的,因为这意味着过滤器同样不会减少它们。唯一能少读的办法,是更窄的键条件或者一个索引。
不管你要什么,一页就是 1 MB。「单次 Query 操作最多读取所设定的最大项目数(如果使用了 Limit 参数),或者最多 1 MB 数据」(抓取于 2026-07-28)。比这更宽的分区总是会分页,这也是上面那个 30 项目的查询从来不是一次调用的原因。
查询索引要多一个参数。--index-name 会把键条件切换到该索引的键上;全局二级索引还会拒绝 --consistent-read。参见用 AWS CLI 查询 GSI。
用可视化的方式来做
在一条命令里同时把键条件、两个占位符映射和分页循环都写对,正是这里的全部难度所在。免费的 DynamoDB 查询构建器会组装这个请求(包括索引和翻页),并把它输出为一条可运行的 CLI 命令。
要针对你自己的表运行查询——键条件表单、随滚动自动翻页的网格、把请求作为 CLI 命令复制出来——请下载 DynoTable。
相关指南
- Query vs. Scan——为什么
query才是正确的默认选择。 - 分页——
LastEvaluatedKey、ExclusiveStartKey,以及为什么Limit不是页大小。 - "Query condition missed key schema element"——键条件写错了属性名,或者漏掉了分区键。
- "Query key condition not supported"——键条件用不了的运算符,比如 contains 或第二个排序键条件。
参考资料
- Query — Amazon DynamoDB API Reference
- query — AWS CLI Command Reference
- Using the pagination options in the AWS CLI — AWS CLI User Guide
- Filtering AWS CLI output — AWS CLI User Guide
- Querying tables — Amazon DynamoDB Developer Guide
2026-07-28 用 aws-cli/2.36.9 针对 9000 端口上的 DynamoDB Local(amazon/dynamodb-local)实测,数据集为一个含 30 个各约 60 KB 项目的分区。上方的计数、token 和容量读数均为原样照录的输出。DynamoDB Local 按文档记载的取整规则计算容量;请把绝对数字当作形态演示,规划容量前请针对真实服务测量你自己的表。