前面两篇讲了索引类型和 ESR 原则,但所有这些设计到底有没有生效,只有一个验证手段:explain。问题是,很多人用 explain 的方式是「跑一下,看有没有 IXSCAN 字样」,看完就走了。这种用法只用了 explain 一成的能力,错过了它真正值钱的部分——查询到底扫了多少、回表了多少、慢在哪

explain 不是一次性命令,它有三个详细程度,每一层回答不同的问题。这一篇讲清楚怎么分层读 explain,以及哪几个指标是判断查询健康的硬指标。

先把机制边界说清楚

explain 有三个详细级别,对应三个问题:

  • queryPlanner(默认):这个查询会怎么执行?看优化器选了哪个计划,但不真正执行。
  • executionStats:这个查询实际代价多大?真正执行一次,返回扫描量、回表量、耗时。
  • allPlansExecution:优化器为什么选这个计划?把所有候选计划都跑一遍,对比代价。

排查慢查询时,几乎总是要从 executionStats 开始——只有它告诉你查询真的扫了多少。queryPlanner 只告诉你「计划长什么样」,但计划可能因为统计信息不准而选错,实际代价和计划设想不一致。

explain 三档

explain 三档:看懂执行计划

三层各有用途:queryPlanner 最快、用于快速确认走没走索引;executionStats 是主力、给出真实代价;allPlansExecution 用于诊断「为什么优化器选了个慢计划」。

必看的几个指标

executionStats 里指标很多,但真正决定查询健康的是这几个:

nReturned:最终返回了多少文档。这是查询的目的,也是衡量其他指标的基准。

totalKeysExamined:扫描了多少索引键。理想情况下它应该接近 nReturned——扫多少返回多少。如果它远大于 nReturned(比如扫了 10 万键返回 100 条),说明索引选择性差,大量扫到的键最后没匹配。

totalDocsExamined:回表读了多少文档。这个指标最关键:

  • 等于 0 → 覆盖查询,索引里就有全部需要的字段,没回表,最快。
  • 接近 nReturned → 健康,几乎每个回表的文档都被返回。
  • 远大于 nReturned → 低效,回表读了大量文档却只返回了少量,索引没起到过滤作用。

executionTimeMillis:总耗时。但更有用的是看它怎么分布在各个 stage——是 IXSCAN 慢、FETCH 慢、还是 SORT 慢。

一个记忆口诀:健康查询的比例是 nReturned : totalKeysExamined : totalDocsExamined ≈ 1 : 1 : 1。任何一项远大于 nReturned,都是优化空间。

怎么判断查询健康

把指标翻译成判断:

看到 COLLSCAN:查询走了全表扫描,没有可用索引。这是第一个红灯——除了极小集合,生产环境的查询不应该出现 COLLSCAN。要么加索引,要么检查查询条件是不是写错了。

看到 SORT stage:排序没走索引,要在内存里排。小结果集没事,大结果集会撞 32MB 上限报错 QueryExceededMemoryLimitNoDiskUseAllowed。解决方法是把排序字段纳入复合索引(按 ESR),让索引天然有序。

totalDocsExamined ≫ nReturned:回表读了大量文档却只返回了少量。要么是索引选择性差(字段值太集中),要么是索引字段顺序不对(按 ESR 调整),要么是查询条件本身就不该走这个索引。

FETCH 占了大头耗时:说明索引定位没问题,但回表读取成了瓶颈。可能是数据不在 Cache(冷数据),也可能是返回字段太多没法覆盖查询。前者是内存问题,后者是索引设计问题。

allPlansExecution:诊断选错计划

有时你会遇到诡异的情况:明明有合适的索引,优化器却选了另一个慢计划。这时要用 allPlansExecution——它让所有候选计划都跑一段(trial period),记录每个的实际代价,你能看到「为什么优化器没选你以为该选的那个」。

优化器选错计划的常见原因:统计信息过时(collStats 里的 cardinality 不准)、数据分布不均(某些值特别集中)、查询条件复杂导致成本估算偏差。5.0 之后 MongoDB 引入了 plan stability 和更稳定的优化器,减少了这类抖动,但仍可能在数据分布特殊时发生。

遇到选错计划,临时的解法是 hint() 强制指定索引,但根本解法是理解为什么优化器估错了,必要时调整索引设计让正确计划明显更优。

一个实际的排查流程

拿到一条慢查询,按这个顺序读 explain:

  1. explain("executionStats") 看走没走索引(IXSCAN vs COLLSCAN)。
  2. totalKeysExaminedtotalDocsExaminednReturned 的比例,判断索引利用效率。
  3. 看有没有 SORT stage,判断排序是不是瓶颈。
  4. 如果索引没选对,用 allPlansExecution 对比候选计划。
  5. 根据诊断结果:缺索引就建、顺序不对就按 ESR 调、回表多就考虑覆盖查询、排序慢就把排序字段纳入索引。

判断框架

  • 排查慢查询第一步永远是 explain("executionStats"),不是猜。
  • 三档各有用途:queryPlanner 看计划、executionStats 看代价、allPlansExecution 看选型。
  • 健康比例 1:1:1:返回数 ≈ 扫键数 ≈ 回表数。
  • COLLSCANSORTtotalDocsExamined ≫ nReturned 是三大红灯。
  • 选错计划用 allPlansExecution 诊断,临时 hint(),根本靠索引设计。
  • explain 是索引优化的「测谎仪」——索引建得好不好,explain 说实话,不要凭感觉。

下一篇会沿着 explain 揭示的一个理想状态——totalDocsExamined = 0 的覆盖查询展开,讲清楚怎么让查询完全不回表。


关于十三Tech

我是十三,All in AI Agent 方向的架构师,专注 AI 工程实践。

我相信 AI 是程序员的最佳搭档,也希望帮助每一位开发者更好地驾驭 AI。

如果你想继续跟完这套「图解 MongoDB」,欢迎关注公众号 「十三Tech」。后续会按索引优化、存储引擎、高可用和分片集群这条线更新。

十三Tech公众号二维码