部分索引是稀疏索引的超集,支持任意bson条件过滤,能精准索引非空值、时间范围等场景,而稀疏索引仅跳过字段完全不存在的文档,对null或空字符串仍索引,且查询易被优化器绕过。

稀疏索引只认“字段是否存在”,部分索引能写任意条件
稀疏索引的过滤逻辑极其简单:sparse: true 仅跳过「字段完全不存在」的文档,对 {email: null} 或 {email: ""} 都照常索引。它不关心值内容,只看键存不存在。
部分索引用 partialFilterExpression,可以写完整 BSON 表达式:{email: {$exists: true, $ne: null}}、{status: "active"}、{"tags.0": {$exists: true}}。它真正按业务语义筛选,不是靠字段存在性碰运气。
- 想索引“所有非空 email”?稀疏索引做不到——它会把
{email: null}也收进去;部分索引一行表达式就能搞定 - 想索引“最近 30 天创建的订单”?稀疏索引完全无能为力;部分索引直接写
{created_at: {$gte: ISODate("...")}} - 字段缺失率高但你又常查
{field: null}?稀疏索引会让这类查询退化为全表扫描;普通索引反而更稳
唯一性约束场景下,sparse: true + unique: true 仍可用但有隐含前提
当你要允许字段缺失、同时保证“存在即唯一”时,db.users.createIndex({email: 1}, {unique: true, sparse: true}) 是合法且有效的。但它只在单分片内生效,且不防 null 冲突——{email: null} 和 {}(无 email 字段)都能插入多次。
- 如果你的
email字段实际存储的是null而非缺失,这个组合毫无意义,甚至引入歧义 - 分片集群下,该索引无法阻止不同分片插入相同
email值——除非email同时是分片键 - 更安全的做法:用部分索引替代,例如
{email: 1}, {unique: true, partialFilterExpression: {email: {$type: "string"}}},显式排除null和缺失
查询计划里看不到 IXSCAN?大概率是稀疏索引被 MongoDB 主动绕过了
MongoDB 默认拒绝用稀疏索引执行可能漏数据的查询。比如 find({score: {$lt: 90}}),只要集合里有文档不含 score 字段,优化器就宁可走 COLLSCAN 也不用稀疏索引——这是保护性设计,不是 bug。
- 用
.explain("executionStats")查看winningPlan.stage,如果是COLLSCAN而不是IXSCAN,先检查该字段是否真稀疏,再确认查询是否覆盖了缺失文档 -
sort({score: 1})用稀疏索引会导致排序结果缺失所有无score的文档,业务上往往不可接受 - 强制用
.hint({score: 1})能绕过,但必须明确接受“结果不全”的后果,不能当作常规手段
从 MongoDB 3.2 起,部分索引就是稀疏索引的超集,优先选它
部分索引能完全覆盖稀疏索引的所有能力,还多出条件表达、类型校验、组合过滤等灵活性。官方文档早已明确推荐:新项目别再新建稀疏索引,改用 partialFilterExpression。
- 迁移成本极低:把
{sparse: true}换成{partialFilterExpression: {"field": {$exists: true}}}即可,行为几乎一致 - 后续扩展方便:今天只筛存在性,明天加个
{$ne: null}或{$regex: "^valid-"},不用改索引结构 - 聚合管道中用
$lookup关联时,部分索引的语义更可控——稀疏索引容易让左连接意外丢行,因为优化器不敢依赖它做全量匹配
真正难处理的不是语法选择,而是搞清“你到底想索引哪些文档”。先跑一遍 db.collection.aggregate([{$group: {_id: "$field", count: {$sum: 1}} }]) 看分布,再决定用 exists 还是更细粒度的条件——这步跳过,后面所有索引都可能是负优化。











