局部索引仅索引满足partialFilterExpression条件的文档,支持$eq、$gt等操作符及$exists: true、$type、顶层$and;查询须覆盖过滤条件才能命中,unique仅在局部生效。

局部索引(Partial Indexes)是 MongoDB 中真正能帮你把索引“做薄”的机制——它不索引全量文档,只索引满足 partialFilterExpression 条件的活跃数据,既省空间又提速查询。
局部索引必须用 partialFilterExpression 指定过滤条件
它不是靠字段是否存在来判断,而是靠一个明确的、MongoDB 支持的过滤表达式。常见可用操作符只有这几类:
-
$eq、$gt、$gte、$lt、$lte -
$exists: true(注意:$exists: false不被支持) -
$type(比如"string"、"date") - 顶层
$and(不能嵌套在其它操作符里)
例如,假设你用 status 字段标记活跃用户:"active" 表示在线/可服务,"inactive" 或缺失表示休眠,则正确写法是:
db.users.createIndex({email: 1}, {partialFilterExpression: {status: "active"}})
错误写法包括:{status: {$ne: "inactive"}}、{status: {$exists: false}}、{$or: [{status: "active"}, {lastLogin: {$gt: ...}}]} —— 这些都会被拒绝或静默忽略。
查询必须“覆盖”索引的过滤条件才能命中局部索引
MongoDB 不会用局部索引去回答可能涉及未索引文档的查询,否则结果就不完整。也就是说,你的查询条件必须隐含或显式包含局部索引的 partialFilterExpression 子集。
比如上面那个 {status: "active"} 的索引:
- ✅ 能用:
db.users.find({email: "a@b.com", status: "active"}) - ✅ 能用:
db.users.find({email: "a@b.com", status: {$in: ["active", "pending"]}})(因为"active"是$in子集) - ❌ 不能用:
db.users.find({email: "a@b.com"})(没提status,可能漏掉非活跃用户) - ❌ 不能用:
db.users.find({email: "a@b.com", status: "inactive"})(和索引过滤条件冲突)
你可以用 .explain("executionStats") 看 stage 是否为 IXSCAN,以及 indexName 是否是你建的那个局部索引名。
结合 unique: true 时,唯一性只在局部生效
这是最容易踩坑的地方:局部唯一索引 ≠ 全局唯一。它只保证“满足过滤条件的那部分文档”里字段值不重复。
比如你建了这个索引:
db.users.createIndex({email: 1}, {partialFilterExpression: {status: "active"}, unique: true})
那么:
- 两个
status: "active"的文档不能有相同email - 但可以存在一个
status: "inactive"的文档,和活跃用户用同一个email - 也可以存在多个
status: null或缺失status的文档共享同一email
如果你本意是“所有用户 email 必须全局唯一”,就别用局部索引加 unique,直接建普通唯一索引;如果真要“仅活跃用户 email 唯一”,那这个行为就是预期的,不是 bug。
局部索引真正的价值不在语法多炫,而在于你能否准确定义“活跃数据”的业务边界,并让查询习惯自然对齐这个边界——一旦定义模糊或查询绕开过滤条件,索引就等于没建。


















