DynamoDB 中的 Query 对比 Scan
Query 按分区键读取单个项集合(可选地用排序键进一步缩小范围);Scan 读取整张表然后再做筛选。它们在 API 里看起来相似,但在计费 —— 和扩展性 —— 上完全不同。
在 DynamoDB 中,什么时候应该用 Query,什么时候用 Scan?
只要能指定所需分区,就用 Query——它只读取单个项集合,且仅对匹配的项计费。只有一次性导出或极小的表才考虑 Scan;Scan 会读取每一个项,并在任何 FilterExpression 运行之前对整张表计费。在真实数据量下,Query 始终更优。
- Query 是有针对性的:你只为匹配分区中的项付费。
- Scan 是穷尽式的:你为读取每一个项付费,然后用一个在读取被计量 之后 才运行的
FilterExpression把大部分丢弃。
在任何有规模的表上,带筛选条件的 Scan 都是那个经典的“为什么我的账单巨大、延迟还比 RDS 差”的暗坑。
并排对比
| Query | Scan | |
|---|---|---|
| 读取 | 一个分区(按 PK) | 表中的每一个项 |
| 计费容量 | 分区中匹配的项 | 整张表,在筛选之前 |
FilterExpression | 在读取之后应用 —— 仍按读取计费 | 同样 —— 筛选从不削减成本 |
| 延迟 | 随表增长保持平稳 | 随表大小增长 |
| 分页 | 1 MB/页 → LastEvaluatedKey | 1 MB/页;可并行化 |
| 适用于 | 已知的访问模式 | 一次性导出、极小的配置表 |
关键陷阱:FilterExpression 在两种操作上都是在 DynamoDB 计量读取之后才运行。一个“返回 10 行”的 Scan 可能为读取一百万行而计费 —— 筛选是一种便利,绝非成本控制手段。
一次全表 Scan 实际要花多少
把数字摆出来。DynamoDB 以 4 KB 为单位计量读取:一次读取每 4 KB 花费一个,一次读取只花一半。Query 和 Scan 累加的是它们触达的每一个项的大小 —— 而不是它们返回的每一个项 —— 并向上取整到下一个 4 KB。
拿一张 100 万个项、平均每项 2 KB 的表(约 2 GB 数据)来说,假设某个访问模式只需要其中 10 个项:
| 读取的项 | 计量的数据 | 读取单元(最终一致) | |
|---|---|---|---|
Scan + FilterExpression | 1,000,000 | ~2 GB | ~262,000 |
对匹配键的 Query | 10 | 20 KB | 3 |
同样是这 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。
归根结底就是一个问题 —— 你能说出你需要的那个分区吗?
如果键已知,就用 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,针对你自己的表运行并检查这些查询 —— 它会显示它所运行的每一个操作消耗的容量。