mongodb布尔查询需显式用$and/$or/$not组合,$or各分支须独立走索引,$expr会完全绕过索引,聚合中应优先用普通字段$match过滤再用$expr精细计算。

如何用 $and、$or、$not 组合多条件而不拖慢查询
MongoDB 的布尔逻辑不是靠 SQL 那套括号嵌套实现的,而是靠显式操作符驱动。直接写 { status: "active", type: { $in: ["A", "B"] }, score: { $gt: 100 } } 默认就是 AND 关系;但一旦需要混合逻辑(比如 “(A 或 B) 且非 C”),就必须用 $and / $or 显式包裹——否则查询会语法报错或语义错位。
常见错误是把 $or 当成“可选条件”随意加:比如 { $or: [{ a: 1 }, { b: 2 }] } 看似简单,但如果 a 和 b 都没索引,整个 $or 就退化为全表扫描。更糟的是,$or 内每个子句必须能独立走索引,MongoDB 才会分别执行再合并结果。
-
$and一般不用显式写(多个顶层字段天然 AND),但当需要强制控制求值顺序或混入$not时必须用,例如:{ $and: [{ status: "active" }, { $not: { archived: true } }] } -
$or中每个分支应至少有一个已建索引的字段,否则该分支被跳过索引;建议用explain("executionStats")确认每个$or分支是否都命中了IXSCAN -
$not不能直接作用于范围操作符(如$not: { $gt: 5 }合法,但$not: { $regex: "..." }无法使用正则索引,会触发 COLLSCAN)
复合索引能否覆盖 $or 查询?哪些字段顺序真关键
不能一概而论。MongoDB 对 $or 的索引策略是“各查各的”,即每个 $or 分支单独走一个索引(可以是同一个复合索引的不同前缀,也可以是不同索引)。所以,想让 { $or: [{ a: 1, b: 2 }, { c: 3, d: 4 }] } 高效,理想情况是存在两个索引:{ a: 1, b: 1 } 和 { c: 1, d: 1 },而不是一个四字段大索引。
容易踩的坑是强行建 { a: 1, b: 1, c: 1, d: 1 } 试图“一索引通吃”——这在 $or 中几乎无用,因为 $or 分支无法共享后缀字段。只有当所有分支都以相同字段开头时,才可能复用前缀,例如:{ $or: [{ a: 1, b: 2 }, { a: 3, c: 4 }] } 可受益于 { a: 1, b: 1, c: 1 },但第二个分支仍只能用到 a 字段。
- 对含
$and的复杂查询(如{ a: 1, $or: [{ b: 2 }, { c: 3 }] }),优先建{ a: 1, b: 1 }和{ a: 1, c: 1 }两个索引,而非{ a: 1, b: 1, c: 1 } - 索引字段顺序影响
$or分支能否命中:若分支中只查b,但索引是{ a: 1, b: 1 },则该分支无法使用该索引(缺少a等值条件) - 用
db.collection.getIndexes()和explain()中的indexBounds字段确认每个分支实际用了哪个索引、扫描范围多大
为什么 $expr 会让布尔逻辑彻底脱离索引覆盖
一旦在查询里用 $expr(比如 { $expr: { $and: [ { $gt: ["$score", "$threshold"] }, { $eq: ["$status", "active"] } ] } }),MongoDB 就无法使用常规字段索引——因为 $expr 是服务端运行的表达式,字段值需先读出再计算,绕过了索引的 B-tree 路径查找。
这不是性能微调问题,而是根本性限制:$expr 查询默认走 collection scan,即使你为 score、status 建了索引也无效。唯一缓解方式是前置过滤:先用普通字段条件缩小结果集,再用 $expr 做精细筛选,例如:{ status: "active", $expr: { $gt: ["$score", "$threshold"] } } 至少能利用 { status: 1 } 索引。
-
$expr中的字段比较(如$gt、$eq)无法利用单字段或复合索引,但某些聚合阶段(如$addFields后的$match)如果字段已物化,仍可走索引 - 替代方案优先考虑应用层计算,或用
$lookup+ 聚合提前算好衍生字段并建索引 - 用
explain("queryPlanner")查看winningPlan中是否有IXSCAN;若只有COLLSCAN,说明$expr已阻断全部索引路径
聚合管道里写布尔逻辑,$match 阶段和 $expr 怎么分工
聚合中布尔逻辑要分两层处理:$match 阶段尽量用普通字段语法(支持索引),把 $expr 留给真正需要字段间运算或动态比较的场景。很多人误以为聚合天然“更强大”,结果把所有条件塞进一个 $match: { $expr: ... },导致整个 pipeline 失去索引加速能力。
典型反例:查 “用户等级 ≥ 5 且注册天数 > 30”,如果 level 和 reg_date 都有索引,就该写成两个 $match:{ level: { $gte: 5 } } 和 { $expr: { $gt: [{ $divide: [{ $subtract: ["$$NOW", "$reg_date"] }, 86400000] }, 30] } }——前者走索引快速过滤,后者只在剩余文档上计算。
- 多个
$match阶段会被 MongoDB 自动合并优化,不必担心“拆开就变慢” -
$expr在$match里用一次,比在$addFields+ 后续$match里重复计算更高效(避免中间字段冗余) - 涉及数组字段的逻辑(如 “tags 包含 A 且不包含 B”),优先用
$all+$ne,而非$expr+$in,前者可走数组索引,后者不能
$or 要求每个分支自洽索引,$expr 直接切断索引链路,而聚合里 $match 的位置稍有偏差,就可能让前面所有索引努力白费。真正起作用的,从来不是“建了索引”,而是“查询写法恰好匹配索引结构”。











