Node.js(AWS SDK v3)中的 DynamoDB Query
在 AWS SDK v3 里,一次完整的 Query 是下面那个 do/while,而不是大多数代码片段展示的那一次 client.send():一页的上限是 1 MB,分区里剩下的部分只有在你把 LastEvaluatedKey 喂回去之后才会到。至于什么时候 Query 才是对的读取方式,见 Query 与 Scan 的取舍。
代码
import {DynamoDBClient, QueryCommand} from '@aws-sdk/client-dynamodb';
const client = new DynamoDBClient({region: 'us-east-1'});
const items = [];
let lastEvaluatedKey;
do {
const response = await client.send(
new QueryCommand({
TableName: 'Music',
KeyConditionExpression: '#hashKey = :hashKeyValue AND begins_with(#rangeKey, :rangeKeyValue)',
ExpressionAttributeNames: {
'#hashKey': 'Artist',
'#rangeKey': 'SongTitle'
},
ExpressionAttributeValues: {
':hashKeyValue': {S: 'Arturo Sandoval'},
':rangeKeyValue': {S: 'C'}
},
ExclusiveStartKey: lastEvaluatedKey
})
);
items.push(...(response.Items ?? []));
lastEvaluatedKey = response.LastEvaluatedKey;
} while (lastEvaluatedKey);
console.log(`Found ${items.length} items`);这个循环实际做了什么
针对一个 600 首歌的样本数据,每首约 3.9 KB、全都在 Artist = "Arturo Sandoval" 之下,上面的循环发出了三次请求:
| 往返次数 | Count | ScannedCount | 读单元 | LastEvaluatedKey |
|---|---|---|---|---|
| 1 | 271 | 271 | 128.5 | 有 |
| 2 | 271 | 271 | 128.5 | 有 |
| 3 | 58 | 58 | 27.5 | 无 |
没人配置过 271。那是 1 MB 用完的位置,所以只要你的项大小变了,页边界就会跟着挪。今天翻一页的分区,在你加一个属性之后就翻两页;而只从一次 send() 读 response.Items 的代码,会在不报任何错的情况下把 600 首歌里的 271 首交给你。
现在给同一个查询加上 Limit: 10 和一个针对 Year 的 FilterExpression:
Count: 0 ScannedCount: 10 ConsumedCapacity: 5 LastEvaluatedKey: set十个项被检查、零个被返回,而这次请求照样花了读容量。Limit 限制的是 DynamoDB 读取的量,过滤器是在那之后才跑的,所以一个按"给我 10 条结果"来设的 Limit,给你的是 0 到 10 条之间的任意数量。
2026-07-28 针对 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失败。 @aws-sdk/client-dynamodb从不替你编组。值以{S: 'Arturo Sandoval'}的形式进去,项也以同样的形式回来。这是不引入 DocumentClient 所付的代价;如果你更愿意写普通 JS 对象,该用的封装是@aws-sdk/lib-dynamodb。- 数字是以字符串的形式完成往返的。用
@aws-sdk/util-dynamodb的unmarshall解组{N: '9007199254740993'}得到的是一个 JSbigint,而不是有损的number;传{wrapNumbers: true}则得到{value: '9007199254740993'}。无论哪种,都不要对一个没做过大小检查的 DynamoDBN调用Number()。 KeyConditionExpression接收分区键上的一个相等条件,外加至多一个排序键条件(=、<、<=、>、>=、BETWEEN、begins_with)。其他任何东西都该放进FilterExpression,而它是在读取之后才跑的。ScanIndexForward: false会反转排序键顺序;默认是升序。IndexName能把同一条命令切换到某个二级索引上。
用可视化的方式来做
DynamoDB 查询构建器会把这整个形态——键条件、名字映射和值映射,以及那个 LastEvaluatedKey 循环——生成为一个可运行的 SDK v3 程序,于是分页就不再是你会忘掉的那一部分。
要在真实的表上用图形界面跑查询,配上键条件表单和分页结果表格,请下载 DynoTable。
相关指南
- Query 与 Scan 的取舍——为什么
Query才是对的默认选择。 - 键条件表达式——每一个合法的分区键/排序键运算符。
- "Query condition missed key schema element"——键条件写错了属性,或者漏了分区键。
- "Query key condition not supported"——键条件用不了的运算符,比如 contains 或第二个排序键条件。