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 可能为读取一百万行而计费 —— 筛选是一种便利,绝非成本控制手段。
使用 Query
Query PK = "USER#42" AND SK begins_with "ORDER#"
如果你发现自己为了回答某个常见访问模式而伸手去用 Scan,那是一个建模信号:添加一个全局二级索引,让该模式变成一次 Query。
归根结底就是一个问题 —— 你能说出你需要的那个分区吗?
如果键已知,就用 Query;如果未知,就加一个 GSI 把它变成已知,只有在没有键能匹配时才退回到 Scan。
Scan 何时没问题
一次性导出、极小的配置表,以及刻意翻遍整张表的后台作业。当你确实必须读取一切时,用 Segment/TotalSegments 把一个 Scan 拆分到多个 worker 上(一次并行 Scan)。
对 DynamoDB 反射式地写 SELECT * FROM table 是同一种反模式披上了 PartiQL 的外衣 —— 它会编译成一次 Scan。当你确实需要跨项分析(一个 GROUP BY、一个 JOIN、一次聚合)时,DynoTable 的 SQL Workbench 会在客户端基于一个有界的结果集运行它们,而不是猛击你的表。
用容量计算器估算任一模式将花费的读取单元,并试用 DynoTable 针对你自己的表运行并查看这些查询。