用 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 一下试试吧。
