用 explain 分析 MongoDB 慢查询

MongoDB 查询一慢,别瞎猜,用 explain 让它自己交代走没走索引、扫了多少文档。这篇讲清 explain 的三种详细模式、执行计划里 nReturned、totalDocsExamined 这些关键参数啥意思、COLLSCAN 和 IXSCAN 差在哪,教你一眼看出慢查询的病根。

MongoDB 里某个查询变慢了,是走了索引没?还是全表扫了?光靠猜没用。

好在 MongoDB 提供了 explain,能让查询自己“交代”它到底是怎么执行的——走没走索引、扫了多少文档、花了多久。

这篇我们就把 explain 怎么用、输出里那堆参数啥意思讲清楚,重点是学会一眼看出慢查询的病根。

文中涉及的字段含义和 explain 模式,参考了 MongoDB 官方文档的 Explain Results 和 Explain Slow Queries,内容经过重新组织表述。

explain 是干嘛的

explain 直译“解释”,它干的事就是让 MongoDB 解释一下这条查询打算怎么跑、实际跑成啥样。

用起来很简单,把它挂在查询前面:

// 分析这条查询:查 1990 年后、评级是 PG 或 PG-13 的电影
db.movies.explain("executionStats").find({
    year: { $gt: 1990 },
    rated: { $in: ["PG", "PG-13"] }
})
​

跑完它会吐出一大坨 JSON,看着吓人,其实真正要看的就那么几个字段。别被唬住。

先选对模式

explain 有三种详细程度(叫 verbosity 模式),给的信息量不一样,选对了才不浪费。

  • queryPlanner(默认):只告诉你打算怎么跑,选了哪个执行计划,但不真的执行。看“计划”够用,看不到真实耗时
  • executionStats:先选出最优计划,然后真的把它跑一遍,返回执行的统计数据。排查慢查询主要用这个
  • allPlansExecution:最详细,除了最优计划的执行数据,连那些被淘汰的候选计划的数据也一并给你

日常排查慢查询,认准 executionStats 就对了——它既告诉你计划长啥样,又给出真实的执行数字,正是我们判断快慢要的东西。

执行计划长什么样

explain 的输出,本质是一棵由“阶段”(stage)组成的树。

怎么理解这棵树?数据是从叶子往根流的:

  • 最底下的叶子节点,负责去碰真实数据(扫集合、或者扫索引)
  • 上面的节点,拿下面传上来的结果再加工(比如排序、取字段)
  • 最顶上的根节点,输出最终结果

每个 stage 就是一道工序。我们最该关心的,是最底下那道工序到底是怎么取数据的——这基本决定了查询快不快。

两个最要命的 stage

底层取数据的 stage,最常见的就两种,一好一坏,一眼就能分出高下。

COLLSCAN 全表扫描

COLLSCAN 是 collection scan 的缩写,意思是集合扫描——一篇篇文档从头翻到尾,挨个看符不符合条件。

看到它,基本就是坏消息。

它意味着这条查询没用上任何索引,只能靠硬扫。集合里有几百万文档,它就得吭哧吭哧翻几百万篇。慢,就慢在这。

出现 COLLSCAN,八成是该建的索引没建,或者查询条件没能命中已有的索引。

IXSCAN 索引扫描

IXSCAN 是 index scan,索引扫描——先在索引里快速定位,再按定位去取对应的文档。

看到它,基本是好消息,说明查询用上索引了。

就像查字典:COLLSCAN 是一页页翻整本字典找字,IXSCAN 是先翻目录定位到页码再翻过去。谁快谁慢,不用多说。

所以排查第一眼:底层 stage 是 IXSCAN 还是 COLLSCAN。 是 COLLSCAN,优化方向立马就有了——考虑加索引。

(顺带一提,IXSCAN 上面常跟着一个 FETCH 阶段,表示“拿着索引定位到的位置回去取完整文档”,这是正常的,不用慌。)

三个关键参数怎么读

光看 stage 还不够精细,executionStats 里有几个数字,是判断查询好坏的硬指标。

重点记这三个:

  • nReturned:这条查询最终返回了多少篇文档
  • totalDocsExamined:为了拿到结果,MongoDB 实际检查(扫描)了多少篇文档
  • totalKeysExamined:扫描了多少个索引键

还有个 executionTimeMillis,就是总耗时(毫秒),最直观的快慢体现,但它只是结果,上面几个才是原因。

怎么用这几个数字判断好坏?记住一条黄金法则:

比较 totalDocsExamined 和 nReturned。

道理很朴素——理想情况下,我扫了几篇、就该返回几篇,扫描量和返回量应该接近。

  • 两者接近(比如扫了 100 篇、返回 98 篇):说明索引很给力,几乎没做无用功,健康
  • totalDocsExamined 远大于 nReturned(比如扫了 100 万篇、只返回 10 篇):说明 MongoDB 扫了海量文档才挑出这么点结果,大量都是白扫的,这就是典型的索引缺失或没生效

再配合看 totalKeysExamined:如果它是 0,而 totalDocsExamined 是个大数,那基本可以坐实——压根没走索引(一个索引键都没查,全靠扫文档),也就是前面说的 COLLSCAN。

串起来看一个例子

光讲字段有点抽象,我们把它们拼一块儿看怎么下判断。

假设你 explain 一条查询,看到这么组数字:

winningPlan.stage:    COLLSCAN        <- 全表扫,没走索引
nReturned:            5               <- 只返回了 5 篇
totalDocsExamined:    2000000         <- 却扫了 200 万篇!
totalKeysExamined:    0               <- 一个索引键都没查
executionTimeMillis:  3200            <- 花了 3.2 秒
​

这几个数字一摆出来,病因就明明白白了:

  • stage 是 COLLSCAN、totalKeysExamined 是 0 → 确认没用索引
  • 扫了 200 万篇只为返回 5 篇 → 99.99% 都是白扫
  • 结果就是 3.2 秒的慢查询

药方也就出来了:给查询条件涉及的字段建个合适的索引。 建完再 explain 一次,理想情况下你会看到 stage 变成 IXSCAN,totalDocsExamined 从 200 万骤降到接近 5,耗时也跟着掉下来。

这就是 explain 的价值——它不光告诉你“慢”,还告诉你“为啥慢”和“该往哪治”。

小结

MongoDB 慢查询的排查,靠 explain 基本能一锤定音。整个套路串下来:

  • 用 explain("executionStats") 跑一遍慢查询,让它交代真实执行情况
  • 先看底层 stage:IXSCAN 是走了索引(好),COLLSCAN 是全表扫(坏)
  • 再看数字:totalDocsExamined 远大于 nReturned 就是在白扫,totalKeysExamined 为 0 说明没走索引
  • 定位到病根后,多半的解法是给合适的字段加索引,然后再 explain 验证效果

说到底,别一遇到慢查询就凭感觉猜。让 explain 把执行过程摊开,扫了多少、返回多少、走没走索引,数字一对比,病根自己就浮出来了。那就挑条可疑的查询,explain 一下试试吧。

更多推荐

章节目录