mongodb 8.0 默认启用多规划器与cbr混合机制选索引:先由多规划器短时试运行候选计划,未决出优胜者时触发cbr按成本函数(基数估计、i/o、内存等)评分,选取总成本最低计划;cbr仅用于高复杂度、多索引竞争激烈的查询。

查询优化器默认用多规划器 + CBR 混合机制选索引
MongoDB 8.0 默认启用「多规划器(multi-planner)+ 基于成本的排名器(CBR)」双机制协同决策,不再只依赖经典多规划器。它先让多规划器在短时间试运行多个候选计划(含不同索引路径),若没找到明显优胜者,就触发 CBR 按成本函数打分——这个成本基于基数估计、I/O 开销、内存使用等综合估算,最终选总成本最低的计划。
关键点是:CBR 不是全量启用,只对“复杂度高、多索引竞争激烈、谓词组合模糊”的查询才介入;绝大多数简单查询仍由多规划器快速决出胜负。
-
explain会绕过整个计划缓存,强制重新生成并评估所有候选索引,但结果不进缓存 - 即使设置了
indexHints,优化器仍可能弃用它——比如 hinted 索引实际扫描行数远超预估,而另一个未 hint 的索引更优 - 复合索引是否被选中,不仅看字段是否匹配,还严格依赖 ESR(Equal → Sort → Range)顺序与查询谓词的实际结构
索引选择失败的典型表现和自查项
常见现象是 winningPlan.stage 显示 COLLSCAN 或 IXSCAN 但 executionStats.nReturned 远小于 executionStats.totalDocsExamined,说明索引没起到过滤作用。
优先检查以下几项:
- 查询谓词字段是否在索引中——注意嵌套字段要写全路径,如
{"address.city": 1}不能靠{"address": 1}支持 - 是否存在类型不匹配:比如索引建在
status: 1(字符串),但查询传的是数字{status: 0},MongoDB 不会隐式转换,直接跳过索引 - 正则表达式是否以
^开头?非前缀匹配(如/abc/)无法走索引,除非是 text index 或 Atlas Vector Search - 用了
$ne、$nin、$not等低选择性操作符,优化器大概率认为全表扫更快
如何用 setQuerySettings 强制约束索引范围
当优化器反复选错索引,又不想改应用代码时,setQuerySettings 是比 hint() 更持久、更可控的干预方式。它按查询结构哈希绑定设置,重启不丢失,且集群级生效。
示例:限制所有匹配 {status: "pending", createdAt: {$gt: ...}} 结构的查询只能用 status_1_createdAt_-1 索引:
db.adminCommand({
setQuerySettings: "hash-abc123...", // 用 explain("queryPlanner").queryPlanner.queryHash 获取
settings: {
indexHints: [{
ns: { db: "myapp", coll: "orders" },
allowedIndexes: ["status_1_createdAt_-1"]
}]
}
})
注意:allowedIndexes 是白名单,不是必选列表;如果指定索引不存在或不匹配查询字段,查询会直接报错 IndexNotFound,而不是退回到集合扫描。
- 用
db.aggregate([{$querySettings: {}}])查当前生效的设置 -
removeQuerySettings删除特定哈希的设置,别误删全局配置 - 不要滥用——过度锁定索引会阻碍后续版本优化器引入的新策略(比如 8.3 的 EXPRESS 阶段)
ESR 规则失效时的索引设计陷阱
ESR(Equal → Sort → Range)是复合索引字段排序的黄金法则,但容易被忽略的前提是:**所有 Equal 字段必须出现在查询中,且值确定、非数组、非 null**。
一旦破坏任一条件,后续字段就无法被索引利用:
- 查
{a: 1, b: {$in: [2,3]}, c: {$gt: 10}},即使有索引{a: 1, b: 1, c: 1},c字段的范围查询也失效——因为$in在语义上属于“多个相等”,不是单值等值,优化器视为 Range 类型提前终止 ESR - 查
{a: null, b: 5},而索引是{a: 1, b: 1},null 值在 B-tree 中位置特殊,可能导致b无法被用于扫描边界 - 字段顺序写反了,比如建了
{b: 1, a: 1}却只查{a: 1},那这个索引完全不可用(前缀不匹配)
真正可靠的验证方式不是看有没有索引,而是跑 explain("executionStats") 看 executionStats.executionStages 里 IXSCAN 的 indexBounds 是否收缩到了预期范围——边界越窄,索引越有效。











