入门阅读约 3 分钟

DynamoDB 中的 Query 对比 Scan

Query 按分区键读取单个项集合(可选地用排序键进一步缩小范围);Scan 读取整张表然后再做筛选。它们在 API 里看起来相似,但在计费 —— 和扩展性 —— 上完全不同。

在 DynamoDB 中,什么时候应该用 Query,什么时候用 Scan?

只要能指定所需分区,就用 Query——它只读取单个项集合,且仅对匹配的项计费。只有一次性导出或极小的表才考虑 Scan;Scan 会读取每一个项,并在任何 FilterExpression 运行之前对整张表计费。在真实数据量下,Query 始终更优。

  • Query 是有针对性的:你只为匹配分区中的项付费。
  • Scan 是穷尽式的:你为读取每一个项付费,然后用一个在读取被计量 之后 才运行的 FilterExpression 把大部分丢弃。

在任何有规模的表上,带筛选条件的 Scan 都是那个经典的“为什么我的账单巨大、延迟还比 RDS 差”的暗坑。

并排对比

QueryScan
读取一个分区(按 PK)表中的每一个
计费容量分区中匹配的项整张表,在筛选之前
FilterExpression在读取之后应用 —— 仍按读取计费同样 —— 筛选从不削减成本
延迟随表增长保持平稳随表大小增长
分页1 MB/页 → LastEvaluatedKey1 MB/页;可并行化
适用于已知的访问模式一次性导出、极小的配置表

关键陷阱:FilterExpression 在两种操作上都是在 DynamoDB 计量读取之后才运行。一个“返回 10 行”的 Scan 可能为读取一百万行而计费 —— 筛选是一种便利,绝非成本控制手段。

一次全表 Scan 实际要花多少

把数字摆出来。DynamoDB 以 4 KB 为单位计量读取:一次读取每 4 KB 花费一个,一次读取只花一半。QueryScan 累加的是它们触达的每一个项的大小 —— 而不是它们返回的每一个项 —— 并向上取整到下一个 4 KB。

拿一张 100 万个项、平均每项 2 KB 的表(约 2 GB 数据)来说,假设某个访问模式只需要其中 10 个项:

读取的项计量的数据读取单元(最终一致)
Scan + FilterExpression1,000,000~2 GB~262,000
对匹配键的 Query1020 KB3

同样是这 10 个项,成本相差五个数量级 —— 而且无论筛选条件匹配到十个项还是一个都没有,这个 Scan 每次运行都要为那 ~262,000 计费。在计费下,这些是直接进账单的请求单元;在表上,一次大 Scan 会与生产流量争夺吞吐量,并可能把生产流量限流成 ProvisionedThroughputExceededException

还有三个让人意外的成本事实:

  • Select: COUNT 并不免费。 一次计数的 Query 或 Scan 消耗的读取容量与把这些项读出来完全相同 —— 只是不把它们返回给你。
  • Limit 限制的是被 评估 的项数,而不是匹配的项数。 与筛选条件搭配时,一页可能空着回来,却仍要为整整一页的读取计费。
  • 你从来不必靠猜。 传入 ReturnConsumedCapacity: TOTAL,每个响应都会报告它刚刚消耗的容量。

项大小计算器看看你自己的项有多重,再用定价计算器把读取单元换算成每月账单。

使用 Query

Query  PK = "USER#42"  AND  SK begins_with "ORDER#"

如果你发现自己为了回答某个常见访问模式而伸手去用 Scan,那是一个建模信号:添加一个全局二级索引,让该模式变成一次 Query

归根结底就是一个问题 —— 你能说出你需要的那个分区吗?

YesNoYesNoAccess patternPartition key known?Query reads one partitionCan a GSI key it?Add a GSIScan reads the whole table

如果键已知,就用 Query;如果未知,就加一个 GSI 把它变成已知,只有在没有键能匹配时才退回到 Scan

Scan 何时没问题

一次性导出、极小的配置表,以及刻意翻遍整张表的后台作业。当你确实必须读取一切时,用 Segment/TotalSegments 把一个 Scan 拆分到多个 worker 上(一次 —— 参见 DynamoDB 并行扫描),并用 LastEvaluatedKey 把它妥善分页(分页指南)。如果问题正是你已经在跑的某个 Scan,为什么 Scan 又慢又贵会带你一步步排查。

对 DynamoDB 反射式地写 SELECT * FROM table 是同一种反模式披上了 PartiQL 的外衣 —— 它会编译成一次 Scan。当你确实需要跨项分析(一个 GROUP BY、一个 JOIN、一次聚合)时,DynoTable 的 SQL Workbench 会在客户端基于一个有界的结果集运行它们,而不是猛击你的表。

试用 DynoTable,针对你自己的表运行并检查这些查询 —— 它会显示它所运行的每一个操作消耗的容量。

更新于