MongoDB中$or查询需每个分支独立索引,因默认不自动索引合并;仅当各分支满足独立谓词、前缀索引匹配且索引可用三条件时,才触发IXSCAN+OR执行模式,否则退化为COLLSCAN。

为什么MongoDB在$or查询中每个分支都需要独立索引
当执行类似 db.orders.find({ $or: [{ status: "paid" }, { amount: { $gt: 1000 } }] }) 的查询时,MongoDB 默认不会把两个条件“合并优化”,而是必须让每个分支各自具备快速定位文档的能力,否则整个 $or 就会退化为全表扫描(COLLSCAN),哪怕其中一个字段已有索引也无济于事。
根本原因:MongoDB不自动做索引合并
MongoDB 的查询优化器不会主动将多个单字段索引“拼接”成一个逻辑上的联合执行计划。它只会在满足严格前提时,才分别启动多个 IXSCAN 子阶段,再把结果集去重合并——这个机制叫「索引合并(Index Intersection)」,但【它不是默认行为,也不是自动触发的】。
换句话说:你建了 {status: 1} 和 {amount: 1} 两个索引,不代表 $or 就能用上它们;必须显式满足三要素,MongoDB 才会真正走两个 IXSCAN → OR → MERGE 的路径。
三个硬性条件缺一不可
方法一:验证是否满足「独立谓词」
每个 $or 分支必须是干净的顶层表达式:不能含 $and、$not、$elemMatch,也不能是数组字段的嵌套匹配。例如 { tags: { $in: ["sale"] } } 可以,但 { $and: [{ status: "paid" }] } 就不行——这会让该分支直接被排除出索引候选。
方法二:检查「前缀索引匹配」
分支 { status: "paid", createdAt: { $gt: ISODate("2025-01-01") } } 要生效,索引必须是 { status: 1, createdAt: -1 },且 status 必须在最左位。如果只建了 { createdAt: -1, status: 1 },该分支就完全无法使用该索引——MongoDB 不会倒着匹配索引字段顺序。
方法三:确认「索引可用性」
运行 db.orders.getIndexes() 查看索引状态,确保没有显示 "building": true 或 "sparse": true 却有缺失字段的文档。稀疏索引遇到字段不存在的文档会跳过,导致 $or 结果漏数据——【这是静默错误,不报错但结果不对】。
实操验证:用 explain 看清真实执行路径
第一步:执行带执行统计的查询db.orders.find({ $or: [{ status: "paid" }, { amount: { $gt: 1000 } }] }).explain("executionStats")
第二步:重点看 executionStages 字段
如果出现 "stage": "OR",且其 "inputStage" 下有两个 "stage": "IXSCAN",说明索引合并成功;如果只有 "stage": "COLLSCAN" 或单个 "IXSCAN" + 大量 "stage": "FETCH",说明至少有一个分支没走索引。
第三步:核对 totalDocsExamined 值
若该值接近集合总文档数,基本可断定发生了全表扫描——此时别调优查询写法,先回去检查索引是否真建对了、建全了、建可用。

















