在DynamoDB的表设计中,稀疏索引(sparse index)是一种利用二级索引只收录部分数据的技巧。它的核心逻辑建立在DynamoDB的索引写入机制上:当某个条目并不包含索引所定义的主键属性时,该条目就不会被投影到这个二级索引里。因此,如果我们把某一类不常出现、但查询频率很高的属性作为索引主键,就能得到一张“瘦身”的索引表。

要理解稀疏索引为什么省钱,得先看清普通全局二级索引(GSI)的代价。常规GSI要求每一行原表数据都至少有一份投影进索引,无论你是否查询它。表越大,索引存储和写入吞吐(WCU)越贵。而稀疏索引只收录“有那个属性”的少数项,索引规模可能只有原表的千分之一。
举一个典型场景:用户行为事件表,每天写入上亿条埋点,其中只有“投诉”事件带有 complaint_type 字段。如果为 complaint_type 建GSI,就只会有投诉事件进入索引。后台查投诉时直接命中索引,不必扫描全表,读写容量都集中在少量数据上。
稀疏索引的基本创建方式
在DynamoDB里,稀疏索引通常通过创建全局二级索引并指定某个可选属性为索引分区键来实现。下面是一段用AWS CLI创建稀疏GSI的示例,假设原表有个可选属性 refund_status,只出现在退款订单中。
aws dynamodb update-table
--table-name Orders
--attribute-definitions AttributeName=refund_status,AttributeType=S
--global-secondary-index-updates
"[
{
"Create": {
"IndexName": "RefundStatusIndex",
"KeySchema": [{"AttributeName":"refund_status","KeyType":"HASH"}],
"Projection": {"ProjectionType":"INCLUDE","NonKeyAttributes":["order_id","amount"]},
"ProvisionedThroughput": {"ReadCapacityUnits":5,"WriteCapacityUnits":5}
}
}
]"
这段代码里,refund_status 并没有被定义为原表的必需属性。只有那些在 Item 里写了 refund_status 的订单,才会被同步进 RefundStatusIndex。其余订单完全不占索引的存储和写入额度。
从数据模型看,稀疏索引非常适配“大头表+稀有状态”的结构。比如设备心跳表里有海量在线记录,只有离线设备带有 offline_reason,那么离线查询就走 offline_reason 的稀疏索引,成本极低。设计时要注意索引键的分布,若稀有属性本身基数过低(例如只有两种值),可能造成索引侧热点分区,需要引入随机后缀或时间分片来打散。
查询稀疏索引的代码示例
使用SDK查询稀疏索引时,操作和普通GSI一致,只是你要确信查询条件对应的属性在目标项中真实存在。以下Python代码演示如何通过 refund_status 查到少量退款订单。
import boto3
client = boto3.client('dynamodb')
resp = client.query(
TableName='Orders',
IndexName='RefundStatusIndex',
KeyConditionExpression='refund_status = :rs',
ExpressionAttributeValues={
':rs': {'S': 'pending'}
}
)
for item in resp['Items']:
# item里只包含带有refund_status且值为pending的订单
print(item['order_id']['S'], item['amount']['N'])
因为索引里只有退款订单,上述 query 的返回集非常小,消耗的读容量单位(RCU)远低于对原表用 filter 做扫描。即便表有十亿行,只要 pending 状态的订单只有几万,索引侧的数据量也就几万行。
不过要小心一种误区:有人以为稀疏索引能加快“任意字段”的查询。其实只有以索引键(本例 refund_status)作为条件才有效。如果你用非索引属性去过滤,DynamoDB仍要在索引内做后置 filter,虽然数据量小了,但并非最优。最佳实践是把高频查询维度直接做成稀疏索引键,或组合稀疏键与排序键。
稀疏索引与全量索引的成本对比
我们可以通过一个简单的容量估算表,看稀疏索引在写入成本上的优势。假设原表每天写入1000万条,其中1%带 refund_status。
| 索引类型 | 每日写入条目 | 索引WCU消耗(估算) |
|---|---|---|
| 全量GSI(所有字段投影) | 1000万 | 1000万 × 1 = 1000万单位 |
| 稀疏索引(仅refund_status项) | 10万 | 10万 × 1 = 10万单位 |
上表说明,稀疏索引把索引写成本压缩到原来的百分之一。在按量计费模式下,这直接反映为账单金额的下降。存储费用也同比例缩减,因为索引不包含那990万条无 refund_status 的记录。
当然,稀疏索引不是银弹。它不适合需要覆盖全表属性的分析查询,也不适合作为唯一强制约束(DynamoDB本身不提供唯一约束)。在需要偶尔全量扫描的业务里,还是得回原表。架构上通常组合使用:热路径走稀疏索引,冷分析走原表或导出到数据湖。
设计稀疏索引的注意事项
第一,索引属性必须在写入时显式存在。若业务代码有时漏写 refund_status,那些订单就会“消失”在索引中,导致查不到。建议在表层用条件写(ConditionExpression)保证关键状态落地。
第二,投影类型要选对。如果查询只需要订单号和金额,用 INCLUDE 投影即可,避免 ALL 投影把整个大文档塞进索引,抵消稀疏带来的体积优势。下面这段Java代码展示条件写并附带索引属性:
Map<String, AttributeValue> item = new HashMap<>();
item.put("order_id", new AttributeValue().withS("O123"));
item.put("refund_status", new AttributeValue().withS("pending"));
item.put("amount", new AttributeValue().withN("99"));
PutItemRequest req = new PutItemRequest()
.withTableName("Orders")
.withItem(item)
.withConditionExpression("attribute_not_exists(refund_status)");
client.putItem(req);
第三,监控索引的读写节流。尽管数据少,若突发大量退款导致 pending 集中写入,仍可能超过预留吞吐。开启按需模式或自动扩缩能缓解该问题。最后,稀疏索引的命名应体现“稀有”语义,方便团队识别其用途,避免被误当作全量索引使用。
DynamoDBsparse_indexNoSQL修改时间:2026-08-11 18:36:36