Node.js 中的 DynamoDB Scan(AWS SDK v3)
下面这个 do/while 不是防御性写法。一个带过滤的 Scan 页面可能带着空的 Items 数组回来,而表里还剩着一大截没扫,所以在第一个响应处就停下,正是一次扫描在明明有匹配项的表上报告零匹配的方式。Query vs. Scan讲了什么时候该彻底避开这个操作。
代码
import {DynamoDBClient, ScanCommand} from '@aws-sdk/client-dynamodb';
const client = new DynamoDBClient({region: 'us-east-1'});
const items = [];
let lastEvaluatedKey;
do {
const response = await client.send(
new ScanCommand({
TableName: 'Music',
FilterExpression: '#filter0 >= :filterValue0',
ExpressionAttributeNames: {
'#filter0': 'Year'
},
ExpressionAttributeValues: {
':filterValue0': {N: '2010'}
},
ExclusiveStartKey: lastEvaluatedKey
})
);
items.push(...(response.Items ?? []));
lastEvaluatedKey = response.LastEvaluatedKey;
} while (lastEvaluatedKey);
console.log(`Matched ${items.length} items`);两个空页面、284.5 个读取单元、8 个项目
这份固定数据是 600 首歌,每首大约 3.9 KB,其中正好有 8 首 Year >= 2010,而且它们排在最后。上面那个循环实际收到的是:
| 往返次序 | Items.length | ScannedCount | 读取单元 | LastEvaluatedKey |
|---|---|---|---|---|
| 1 | 0 | 271 | 128.5 | 有 |
| 2 | 0 | 271 | 128.5 | 有 |
| 3 | 8 | 58 | 27.5 | 无 |
连续两页什么都没返回,而且各花掉 128.5 个读取单元。写成 if (!response.Items.length) return 的代码会报告一张空表。API 参考把规则说得很直白:"a scan result can result in no items meeting the criteria and the Count will result in zero",另外还有一句"a FilterExpression is applied after the items have already been read; the process of filtering does not consume any additional read capacity units"。
第二句话请照着你的账单来读。过滤是免费的,而它丢掉的一切不是:284.5 个读取单元换来 8 个项目,和你完全不加过滤时付的账单一模一样。
2026-07-28 针对 9000 端口上的 DynamoDB Local(amazon/dynamodb-local),使用 node v24.18.0 上的 @aws-sdk/client-dynamodb 3.1095.0 实测。计数与容量均为引擎自己的响应字段。
说明
- 第一趟里
ExclusiveStartKey: lastEvaluatedKey是undefined。v3 的序列化器会丢掉值为undefined的成员,所以同一个对象字面量既能覆盖第一次请求,也能覆盖后续每一次。改成传{}会失败并报ValidationException: The provided starting key is invalid。 response.Items ?? []是在干实事。把它和上面那张表合起来看:空值合并让累加器在那些什么都没匹配到的页面上保持诚实,而while让循环越过它们继续活着。#filter0不是装饰。Year在 AWS 的保留字清单上,不做别名直接用会返回ValidationException: Invalid FilterExpression: Attribute name is a reserved keyword; reserved keyword: Year。Limit数的是读到的项目,不是返回的项目。在这个过滤条件下,Limit: 10得到的是Count: 0和ScannedCount: 10。它是应对容量尖峰的节流旋钮,不是"给我十条结果"的办法。Segment/TotalSegments会把一次全表扫描分摊到多个工作单元上。那切的是墙上时钟时间,不是成本——花掉的还是那 284.5 个单元,只是更快、更并发。
这在一张真实的表上要花多少钱
284.5 个读取单元换 8 个项目,这就是问题的形状,而且它随表线性增长,不随结果增长。在把一次带过滤的扫描放到热路径上之前,先在 DynamoDB 定价计算器里按你的项目大小和流量给整表读取标个价,然后拿它跟一个能把同样的访问模式变成 Query 的 GSI 比一比。
想在图形界面里探索表、用带过滤和分页的结果网格来看,就下载 DynoTable,而不是在脚本里盲扫。
相关指南
- Query vs. Scan——什么时候(很少)用
Scan才说得过去。 - 为什么我的 DynamoDB Scan 又慢又贵?——成本模型以及如何避开它。
- DynamoDB ProvisionedThroughputExceededException——全表扫描会对一张预置容量表的容量做什么。
- DynamoDB ThrottlingException——另一种限流,以及指数退避如何应对它。