mongodb 的 $or 查询慢,因其默认不触发索引交集,多数情况下退化为全表扫描;仅当满足严格条件(如≤4.0版本+独立单字段索引,或共用复合索引前缀)时才可能生效,推荐改用归一化数组字段+单索引方案。

MongoDB 的 $or 查询为什么慢?
因为 $or 默认不触发索引交集(Index Intersection),除非所有分支都命中**同一个复合索引**或各分支能各自走独立索引且满足严格条件。多数人写 { $or: [ { a: 1 }, { b: 2 } ] },却只给 a 和 b 分别建了单字段索引,结果 MongoDB 80% 情况下会退化为 collection scan——尤其在 4.2+ 版本中,索引交集能力被大幅弱化,甚至默认关闭。
什么时候能用索引交集?
仅限于以下两种情形之一成立:
- MongoDB 版本 ≤ 4.0,且每个
$or分支都命中一个**独立的单字段索引**(如{a: 1}和{b: 1}),且查询不含排序、$regex、$text等干扰项 - 所有分支共用同一个**复合索引前缀**,例如
{ $or: [ { a: 1, b: 2 }, { a: 3, c: 4 } ] },配合索引{ a: 1, b: 1, c: 1 }—— 这时靠a前缀驱动,再用index bounds裁剪
注意:$or 中混用范围查询(如 { a: { $gt: 5 } })和等值查询(如 { b: "x" }),大概率让索引交集失效;4.2+ 默认禁用交集,需显式启用 db.collection.find().hint({ $and: [ {a: 1}, {b: 1} ] })(不推荐,不稳定)
更靠谱的替代方案:合并字段 + 单索引
与其依赖不可控的索引交集,不如把多个 OR 条件“收口”到一个字段上。常见做法:
- 加一个
search_tags数组字段,存归一化标签,如{ search_tags: ["user_123", "org_456", "status_active"] },然后建索引{ search_tags: 1 },查{ search_tags: { $in: ["user_123", "org_456"] } } - 对低基数字段(如状态、类型)用位图或枚举编码,存在一个整数字段里,配合
$bitsAllSet查 - 业务允许时,把 OR 拆成多次查询,在应用层合并结果(适合分支少、数据量可控场景)
示例:原查询 { $or: [ { userId: "u1" }, { orgId: "o1" }, { status: "active" } ] } → 改为插入时写入 search_keys: ["u_u1", "o_o1", "s_active"],查 { search_keys: { $in: ["u_u1", "o_o1", "s_active"] } },配合 { search_keys: 1 } 索引,性能稳定且可预测
单独建索引真没用?
单独建索引不是完全没用,但效果高度受限:
- 只有当
$or中**某个分支能过滤掉 95%+ 文档**,且该字段有高选择性(如唯一 ID、邮箱),MongoDB 才可能选它做主索引,其余分支走 in-memory filter - 如果各字段分布均匀(比如
status只有 3 个值),单字段索引基本无效——status: "active"可能扫全表 1/3 -
explain("executionStats")里看nReturned和totalDocsExamined差距大,就说明索引没真正起作用
真正容易被忽略的是:MongoDB 对 $or 的执行计划不透明,explain 输出里不会明确说“用了索引交集”,而是显示 stage: "OR" 下嵌套多个 IXSCAN —— 但这个嵌套只是计划阶段,运行时仍可能 fallback 到 COLLSCAN,必须实测 executionStats 才算数










