Node.js(AWS SDK v3)中查询 DynamoDB GSI
一次 GSI 查询就是普通的 Query 加上 IndexName,然后有两件事不再像表查询那样行事:你习惯的那个一致性标志变成了错误,而分页游标会多出一个属性。这里 AlbumTitle-index 按专辑取歌曲,而基表(Artist + SongTitle)不做扫描是办不到的。
代码
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',
IndexName: 'AlbumTitle-index',
KeyConditionExpression: '#hashKey = :hashKeyValue',
ExpressionAttributeNames: {
'#hashKey': 'AlbumTitle'
},
ExpressionAttributeValues: {
':hashKeyValue': {S: 'Danzon'}
},
ExclusiveStartKey: lastEvaluatedKey
})
);
items.push(...(response.Items ?? []));
lastEvaluatedKey = response.LastEvaluatedKey;
} while (lastEvaluatedKey);
console.log(`Found ${items.length} songs on the album`);游标有三个属性宽,不是两个
拿一张专辑下的 300 首歌跑上面的循环,看看它交回来的 LastEvaluatedKey:
table query -> ['Artist', 'SongTitle']
GSI query -> ['AlbumTitle', 'Artist', 'SongTitle']GSI 的键不唯一,所以光靠索引键无法续读。DynamoDB 会把索引键和基表键一起返回,两者都必须原封不动地放回 ExclusiveStartKey。这就是为什么一个只存"我看到的最后一个排序键"的手写游标在表上能用,在索引上却会悄悄丢项或重复项——也是为什么当表键是一个你并不想泄露的用户 id 时,把那个键持久化到客户端是个坏主意。
ConsistentRead: true 是 400,不是升级
直觉是:强一致读花更多容量,换来更新的数据。在 GSI 上,它花掉的是你的这次请求:
ValidationException: Consistent reads are not supported on global secondary indexes
HTTP 400API 参考同样直白:"Strongly consistent reads are not supported on global secondary indexes. If you query a global secondary index with ConsistentRead set to true, you will receive a ValidationException." GSI 上的 Scan 会用同样的消息拒绝同一个标志。本地二级索引则是接受它的。
2026-07-28 针对 DynamoDB Local(amazon/dynamodb-local)、在 node v24.18.0 上用 @aws-sdk/client-dynamodb 3.1095.0 复现。错误文本和键的形状都是引擎自己的输出。
说明
IndexName不会取代TableName。两者放在同一条命令里,而KeyConditionExpression这时命名的是索引的分区键(AlbumTitle),不是表的分区键,可用的运算符集合和表查询一样。- 你拿到的只有投影里的东西。GSI 查询返回的是索引所投影的内容(
ALL、KEYS_ONLY或INCLUDE列表),按 API 参考的说法:"global secondary index queries cannot fetch attributes from the parent table"。缺属性意味着要在基表键上再做一次GetItem,或者放宽投影并重建索引。 - 缺少索引键的项永远不会出现。这就是稀疏索引模式,而且它是个特性:只给
status = "OPEN"的行建索引,GSI 就能保持很小。这也是为什么 GSI 查询可能返回比你预期更少的项,却不报任何错。 - 复制是异步的,所以刚落到表上的写入可能还没进索引。在"写后读"的路径上要为此留出余量,而不是在紧凑循环里重试。
用可视化的方式来做
事后再加 GSI 是学会这件事的昂贵方式。单表设计规划器会接过你的访问模式,算出其中哪些需要索引键、哪些基表本来就能满足。
要浏览一张表的索引、从表单里运行 GSI 查询、并用分页表格看结果,请下载 DynoTable。
相关示例
- Python 中查询 DynamoDB GSI——用 boto3 做同一次索引查询。
- 用 AWS CLI 查询 DynamoDB GSI——从 shell 里做同一次索引查询。
- Node.js 中的 DynamoDB Query——查询基表。
- GSI 与 LSI 的取舍——哪种索引类型适合这个访问模式。
- 为什么 GSI 是最终一致性的——复制延迟的来龙去脉。
- "The table does not have the specified index"——索引名对不上(GSI 名区分大小写)。
- "Consistent reads are not supported on global secondary indexes"——为什么强一致读标志在 GSI 上会失败。