布尔字段单建索引基本无效,因其仅含true/false两个值,b-tree索引区分度极低,优化器多跳过而执行collscan;应将其置于复合索引的合适位置或改用部分索引。

直接给布尔字段建单字段索引几乎没用,MongoDB 优化器大概率跳过它,执行 explain() 会看到 stage: "COLLSCAN" 或 indexBounds: [false, true] ——这等于全扫。
为什么布尔字段单建索引基本无效
区分度只有两个值,B-tree 索引无法有效剪枝。即使 status: true 只占 1%,MongoDB 仍可能判定走索引不如直接 scan,尤其当文档体积小、内存充足时。
-
totalDocsExamined接近集合总文档数,nReturned却很小 → 索引未真正过滤 - 写入开销白增:每次插入/更新都要维护额外索引项
- 内存占用上升:索引本身也要进 WiredTiger cache,挤占热数据空间
复合索引中布尔字段该放哪一位
顺序决定它能不能被用上。MongoDB 只能高效使用「前缀连续」的等值条件,所以位置必须匹配查询模式。
- 如果查询总是带
isDeleted: false(例如所有 API 默认排除软删),且搭配高选择性字段如userId,就把它放最左:{isDeleted: 1, userId: 1, createdAt: -1} - 如果
isArchived是可选条件(有时有,有时没有),而tenantId和type更高频、更高选择性,那就把它挪到右边:{tenantId: 1, type: 1, isArchived: 1} - 排序字段必须紧跟等值字段之后;若把
isPublished: 1放在createdAt: -1后面,.sort({createdAt: -1})就无法利用索引排序,触发内存排序
比复合索引更优的替代方案:部分索引
当某个布尔值或低基数枚举值被高频查询(比如 90% 查询都是 status: "done"),部分索引比普通索引高效得多 —— 它只索引满足条件的子集。
- 创建方式:
db.orders.createIndex({status: 1}, {partialFilterExpression: {status: "done"}}) - 必须是精确匹配:
partialFilterExpression不支持$in、正则、范围,否则索引不生效 - 注意副作用:
db.orders.count()不包含未命中partialFilterExpression的文档;聚合中需显式加{$match: {status: "done"}}才一致 - 多个高频值可以分别建,但别堆砌 —— 每个部分索引都增加写放大和内存压力
千万别碰的雷区
布尔字段不是不能索引,而是不能“裸索引”。最容易被忽略的一点是:一旦用了 $or、$not、$expr 或位运算(如 $bitsAllSet),哪怕字段上有索引,整个查询也大概率退化为 COLLSCAN。
-
$or: [{isPublished: true}, {isDraft: true}]要求每个分支都能独立走索引;若只建了{isPublished: 1},第二个分支就只能 scan -
$not: {isDeleted: true}本身可用索引,但换成$not: {createdAt: {$lt: ...}}就不行 - 任何涉及
$expr的布尔逻辑(比如{$expr: {$eq: ["$a", "$b"]}})彻底脱离索引覆盖范围











