$or查询易变慢因默认不自动索引合并,须每个分支独立可索引且索引覆盖全字段,否则退化为COLLSCAN;满足三条件(独立谓词、前缀索引匹配、索引可用)才触发IXSCAN+OR执行模式。
因为 MongoDB 默认不会为 $or 中的多个条件分别走索引再合并结果,除非每个分支都满足「独立可索引」且索引能覆盖全部筛选字段。否则它会退化成 COLLSCAN —— 比如 db.orders.find({ $or: [{ status: "paid" }, { amount: { $gt: 1000 } }] }),即使 status 和 amount 各有单字段索引,MongoDB 也大概率只选其一,甚至全表扫。
必须同时满足三个硬性条件:
$or 分支必须是「独立谓词」:不能嵌套 $and、$not,也不能含数组操作符(如 $elemMatch){ a: 1 },就得有 { a: 1 };分支是 { b: 2, c: 3 },就得有 { b: 1, c: 1 }(顺序要一致)满足后,.explain("executionStats") 里会出现 "stage": "OR",且子节点是多个 "stage": "IXSCAN" —— 这才是真正的索引合并。
这些情况会让索引合并直接失效:
$or 里混用了范围查询和等值查询,但索引顺序不对:比如分支是 { type: "refund", createdAt: { $gt: ISODate(...) } },却只建了 { createdAt: 1, type: 1 } —— 等值字段 type 必须放索引最左位sparse: true)但文档缺失该字段:该文档会被跳过,导致 $or 结果不全$text 索引参与 $or:MongoDB 不支持 $text 和其他索引混合在同一个 $or 中当索引合并不可控时,优先考虑这些更稳的写法:
db.orders.find({ status: "paid" }) 和 db.orders.find({ amount: { $gt: 1000 } }),用 Set 合并 ObjectId$or 请求实际集中在某一个分支(比如多数是查 status: "paid"),就建 { status: 1, amount: 1 },再配合 .hint() 强制走它$or 组合的结果集 ID,避免每次穿透到 MongoDB真正难的不是建索引,而是确认每个 $or 分支在真实数据分布下是否具备高区分度 —— 低基数字段(如只有 "paid"/"pending"/"failed" 的 status)单独建索引效果极差,这时候合并也没用。