DynamoDB全局二级索引 #
一、GSI概述 #
1.1 什么是GSI #
全局二级索引(Global Secondary Index)是一种允许使用与表不同的分区键和排序键查询数据的索引。
text
GSI特点:
├── 分区键可与表不同
├── 排序键可选
├── 跨分区查询
├── 默认最终一致性
├── 独立的容量配置
└── 最多20个/表
1.2 GSI vs 表主键 #
| 特性 | 表主键 | GSI |
|---|---|---|
| 分区键 | 必须指定 | 可选择任意属性 |
| 排序键 | 可选 | 可选 |
| 唯一性 | 必须唯一 | 不要求唯一 |
| 一致性 | 强一致/最终一致 | 最终一致(默认) |
二、创建GSI #
2.1 创建表时定义GSI #
使用CLI:
bash
aws dynamodb create-table \
--table-name Users \
--attribute-definitions \
AttributeName=UserId,AttributeType=S \
AttributeName=Email,AttributeType=S \
--key-schema \
AttributeName=UserId,KeyType=HASH \
--global-secondary-indexes \
'[
{
"IndexName": "EmailIndex",
"KeySchema": [
{"AttributeName": "Email", "KeyType": "HASH"}
],
"Projection": {"ProjectionType": "ALL"},
"ProvisionedThroughput": {
"ReadCapacityUnits": 5,
"WriteCapacityUnits": 5
}
}
]' \
--billing-mode PAY_PER_REQUEST
使用JavaScript SDK:
javascript
const { CreateTableCommand } = require('@aws-sdk/client-dynamodb');
const command = new CreateTableCommand({
TableName: 'Users',
AttributeDefinitions: [
{ AttributeName: 'UserId', AttributeType: 'S' },
{ AttributeName: 'Email', AttributeType: 'S' },
{ AttributeName: 'CreatedAt', AttributeType: 'S' }
],
KeySchema: [
{ AttributeName: 'UserId', KeyType: 'HASH' }
],
GlobalSecondaryIndexes: [
{
IndexName: 'EmailIndex',
KeySchema: [
{ AttributeName: 'Email', KeyType: 'HASH' }
],
Projection: {
ProjectionType: 'ALL'
}
},
{
IndexName: 'CreatedAtIndex',
KeySchema: [
{ AttributeName: 'CreatedAt', KeyType: 'HASH' }
],
Projection: {
ProjectionType: 'INCLUDE',
NonKeyAttributes: ['UserId', 'Name', 'Email']
}
}
],
BillingMode: 'PAY_PER_REQUEST'
});
2.2 向现有表添加GSI #
bash
aws dynamodb update-table \
--table-name Users \
--attribute-definitions \
AttributeName=Phone,AttributeType=S \
--global-secondary-index-updates \
'[
{
"Create": {
"IndexName": "PhoneIndex",
"KeySchema": [
{"AttributeName": "Phone", "KeyType": "HASH"}
],
"Projection": {"ProjectionType": "ALL"}
}
}
]'
javascript
const { UpdateTableCommand } = require('@aws-sdk/client-dynamodb');
const command = new UpdateTableCommand({
TableName: 'Users',
AttributeDefinitions: [
{ AttributeName: 'Phone', AttributeType: 'S' }
],
GlobalSecondaryIndexUpdates: [
{
Create: {
IndexName: 'PhoneIndex',
KeySchema: [
{ AttributeName: 'Phone', KeyType: 'HASH' }
],
Projection: {
ProjectionType: 'ALL'
}
}
}
]
});
2.3 带排序键的GSI #
javascript
const command = new CreateTableCommand({
TableName: 'Orders',
AttributeDefinitions: [
{ AttributeName: 'OrderId', AttributeType: 'S' },
{ AttributeName: 'CustomerId', AttributeType: 'S' },
{ AttributeName: 'OrderDate', AttributeType: 'S' }
],
KeySchema: [
{ AttributeName: 'OrderId', KeyType: 'HASH' }
],
GlobalSecondaryIndexes: [
{
IndexName: 'CustomerOrderIndex',
KeySchema: [
{ AttributeName: 'CustomerId', KeyType: 'HASH' },
{ AttributeName: 'OrderDate', KeyType: 'RANGE' }
],
Projection: {
ProjectionType: 'ALL'
}
}
],
BillingMode: 'PAY_PER_REQUEST'
});
三、投影类型 #
3.1 投影类型说明 #
| 类型 | 说明 | 存储成本 |
|---|---|---|
| KEYS_ONLY | 仅索引键和表主键 | 最低 |
| INCLUDE | 键+指定属性 | 中等 |
| ALL | 所有属性 | 最高 |
3.2 KEYS_ONLY #
javascript
{
IndexName: 'EmailIndex',
KeySchema: [
{ AttributeName: 'Email', KeyType: 'HASH' }
],
Projection: {
ProjectionType: 'KEYS_ONLY'
}
}
适用场景:
text
适用场景:
├── 仅需要主键回表查询
├── 存储成本敏感
└── 索引属性较少
3.3 INCLUDE #
javascript
{
IndexName: 'EmailIndex',
KeySchema: [
{ AttributeName: 'Email', KeyType: 'HASH' }
],
Projection: {
ProjectionType: 'INCLUDE',
NonKeyAttributes: ['Name', 'Status', 'CreatedAt']
}
}
适用场景:
text
适用场景:
├── 需要部分属性
├── 平衡存储和性能
└── 特定查询模式
3.4 ALL #
javascript
{
IndexName: 'EmailIndex',
KeySchema: [
{ AttributeName: 'Email', KeyType: 'HASH' }
],
Projection: {
ProjectionType: 'ALL'
}
}
适用场景:
text
适用场景:
├── 需要所有属性
├── 避免回表查询
└── 查询性能优先
四、查询GSI #
4.1 基本查询 #
javascript
const { QueryCommand } = require('@aws-sdk/lib-dynamodb');
const response = await docClient.send(new QueryCommand({
TableName: 'Users',
IndexName: 'EmailIndex',
KeyConditionExpression: 'Email = :email',
ExpressionAttributeValues: {
':email': 'john@example.com'
}
}));
4.2 带排序键查询 #
javascript
const response = await docClient.send(new QueryCommand({
TableName: 'Orders',
IndexName: 'CustomerOrderIndex',
KeyConditionExpression: 'CustomerId = :cid AND OrderDate BETWEEN :start AND :end',
ExpressionAttributeValues: {
':cid': 'customer001',
':start': '2024-01-01',
':end': '2024-12-31'
},
ScanIndexForward: false // 降序
}));
4.3 过滤表达式 #
javascript
const response = await docClient.send(new QueryCommand({
TableName: 'Orders',
IndexName: 'CustomerOrderIndex',
KeyConditionExpression: 'CustomerId = :cid',
FilterExpression: 'TotalAmount > :minAmount',
ExpressionAttributeValues: {
':cid': 'customer001',
':minAmount': 100
}
}));
五、稀疏索引 #
5.1 概念 #
稀疏索引只包含有索引键属性的项目。
text
稀疏索引特点:
├── 仅索引有键属性的项目
├── 适合可选属性
├── 减少索引大小
└── 提高查询效率
5.2 示例 #
javascript
// 创建稀疏索引
{
IndexName: 'PhoneIndex',
KeySchema: [
{ AttributeName: 'Phone', KeyType: 'HASH' }
],
Projection: {
ProjectionType: 'ALL'
}
}
// 只有有Phone属性的用户会被索引
// 写入数据
await docClient.send(new PutCommand({
TableName: 'Users',
Item: {
UserId: 'user1',
Name: 'User One',
Phone: '+86-138-xxxx-xxxx' // 有Phone属性
}
}));
await docClient.send(new PutCommand({
TableName: 'Users',
Item: {
UserId: 'user2',
Name: 'User Two'
// 无Phone属性,不会被索引
}
}));
// 查询PhoneIndex只会返回user1
5.3 应用场景 #
text
应用场景:
├── 可选属性查询
├── 状态过滤
├── 分类查询
└── 减少索引存储
六、GSI容量管理 #
6.1 按需模式 #
javascript
// GSI继承表的计费模式
{
BillingMode: 'PAY_PER_REQUEST'
}
6.2 预置模式 #
javascript
{
GlobalSecondaryIndexes: [
{
IndexName: 'EmailIndex',
KeySchema: [
{ AttributeName: 'Email', KeyType: 'HASH' }
],
Projection: { ProjectionType: 'ALL' },
ProvisionedThroughput: {
ReadCapacityUnits: 10,
WriteCapacityUnits: 5
}
}
]
}
6.3 更新GSI容量 #
bash
aws dynamodb update-table \
--table-name Users \
--global-secondary-index-updates \
'[
{
"Update": {
"IndexName": "EmailIndex",
"ProvisionedThroughput": {
"ReadCapacityUnits": 20,
"WriteCapacityUnits": 10
}
}
}
]'
七、删除GSI #
7.1 删除GSI #
bash
aws dynamodb update-table \
--table-name Users \
--global-secondary-index-updates \
'[
{
"Delete": {
"IndexName": "PhoneIndex"
}
}
]'
javascript
const command = new UpdateTableCommand({
TableName: 'Users',
GlobalSecondaryIndexUpdates: [
{
Delete: {
IndexName: 'PhoneIndex'
}
}
]
});
八、GSI限制 #
8.1 数量限制 #
text
限制:
├── 每表最多20个GSI
├── 可申请提高到最多50个
├── 每个GSI最多5个属性定义
└── 创建GSI时表状态必须为ACTIVE
8.2 大小限制 #
text
大小限制:
├── 索引键大小:最大2048字节
├── 项目大小:无限制(表限制400KB)
└── 索引项目大小:无限制
8.3 一致性限制 #
text
一致性限制:
├── 默认最终一致性
├── 读取延迟通常<1秒
└── 不支持强一致性读取
九、GSI设计模式 #
9.1 GSI过载模式 #
一个GSI支持多种查询模式:
javascript
// 表设计
{
PK: 'USER#12345',
SK: 'PROFILE',
GSI1PK: 'EMAIL#john@example.com',
GSI1SK: 'USER#12345'
}
// 另一种项目类型
{
PK: 'ORDER#001',
SK: 'DETAIL',
GSI1PK: 'STATUS#PENDING',
GSI1SK: '2024-01-01T10:00:00'
}
// 一个GSI支持多种查询
// 查询Email
await query('GSI1', 'EMAIL#john@example.com');
// 查询状态
await query('GSI1', 'STATUS#PENDING');
9.2 复合排序键模式 #
javascript
// 排序键设计:STATUS#PRIORITY#TIMESTAMP
{
PK: 'PROJECT#001',
SK: 'TASK#001',
GSI1PK: 'PROJECT#001',
GSI1SK: 'OPEN#HIGH#2024-01-01T10:00:00'
}
// 查询高优先级开放任务
await docClient.send(new QueryCommand({
TableName: 'Tasks',
IndexName: 'GSI1',
KeyConditionExpression: 'GSI1PK = :pk AND begins_with(GSI1SK, :prefix)',
ExpressionAttributeValues: {
':pk': 'PROJECT#001',
':prefix': 'OPEN#HIGH'
}
}));
十、最佳实践 #
10.1 GSI设计建议 #
text
设计建议:
├── 仅创建必要的索引
├── 选择合适的投影类型
├── 考虑稀疏索引
├── 避免热点分区键
└── 监控索引使用情况
10.2 性能优化 #
text
性能建议:
├── 使用索引覆盖查询
├── 合理设置投影类型
├── 监控索引容量
├── 使用Auto Scaling
└── 避免过多索引
10.3 成本优化 #
text
成本建议:
├── 使用KEYS_ONLY或INCLUDE减少存储
├── 监控索引使用率
├── 删除不使用的索引
├── 使用稀疏索引
└── 合理配置容量
十一、总结 #
GSI要点:
| 特性 | 说明 |
|---|---|
| 分区键 | 可选择任意属性 |
| 排序键 | 可选 |
| 投影类型 | KEYS_ONLY/INCLUDE/ALL |
| 一致性 | 最终一致 |
| 数量限制 | 20个/表 |
下一步,让我们学习本地二级索引!
最后更新:2026-03-27