线上 MongoDB 慢了,最本能的反应是「加个索引试试」。但这是排查慢查询里最坏的开局——既可能加错索引(没解决根因),又可能加多索引(拖慢写入)。正确的做法是先定位慢查询,搞清楚它慢在哪一层,再有针对性地动手。
前面几篇讲了索引类型、ESR、explain、覆盖查询,这些都是「武器」。这一篇把它们串成一套慢查询排查闭环:怎么发现慢查询、怎么分层定位、怎么收敛到具体修复动作、怎么验证有效。这套闭环是索引与查询优化这一阶段的实战收尾。
先把机制边界说清楚
慢查询排查要分三步走,缺一环都会让优化失效:
- 发现:知道哪些查询慢,慢到什么程度。
- 定位:搞清楚慢查询卡在执行链路的哪一段。
- 修复与验证:对症动手,改完用 explain 验证,持续监控。
很多人的问题出在跳过了「定位」直接「修复」,结果改了索引却发现慢查询还在——因为根因不在索引,而在数据量、网络或应用层。
慢查询排查闭环
这张图把整个闭环铺开:先用 Profile/慢日志发现,再用 explain 分层定位,然后按五条分支收敛到具体动作,最后验证 + 持续监控。
第一步:发现慢查询
MongoDB 提供几种发现慢查询的手段:
Profiler(数据库级探查器)。db.setProfilingLevel(1, {slowms: 100}) 会把超过 100ms 的操作记到 system.profile 集合。查它就能拿到慢查询的完整文本、耗时、扫描量。注意 Profiler 本身有开销(每条操作都记录的话 Level 2 会明显拖慢),生产环境用 Level 1(只记慢操作)。
慢日志(mongod 日志)。mongod 默认会记录超过 slowms(默认 100ms)的操作到日志,格式包含命令、耗时、扫描文档数。grep 日志或用日志聚合工具都能发现慢查询。
mongostat / db.currentOp()。mongostat 看全局 opcounters,能发现「某段时间 query 飙高」。db.currentOp() 看当前正在跑的长操作,适合抓「正在卡住的查询」。
发现慢查询的关键是常态化监控慢日志阈值,而不是等用户投诉才查。慢查询随数据量增长会逐渐浮现,不监控就会被它追着跑。
第二步:用 explain 分层定位
拿到慢查询后,第一件事是 explain("executionStats")。这一篇不重复 explain 的细节(第 09 篇已展开),只讲怎么把 explain 的结果收敛成判断。看几个关键信号:
COLLSCAN:全表扫描,没有可用索引。第一优先级解决。totalKeysExamined ≫ nReturned:扫了大量索引键却只返回少量,索引选择性差或字段顺序不对。totalDocsExamined ≫ nReturned:回表读了大量文档却只返回少量,索引没起到过滤作用或需要覆盖查询。SORTstage:排序在内存做,没走索引,大结果集会撞 32MB 上限。executionTimeMillis高但扫描量不大:可能慢在网络传输、结果集太大,或数据不在 Cache。
第三步:五条修复分支
根据 explain 的信号,慢查询通常落到下面五条分支之一,每条对应不同的修复动作:
分支一:COLLSCAN → 建索引。 这是最高优先级。按 ESR 原则建复合索引,让查询从全表扫变成索引扫。但建索引前要确认这个查询是高频的——低频查询偶尔全表扫,不一定值得为它建索引。
分支二:索引没用全 → 调字段顺序。 有索引但 keysExamined 远超返回数,多半是字段顺序不符合 ESR。重新排索引字段(等值在前、排序居中、范围在尾),让索引能完整使用。
分支三:回表太多 → 覆盖查询或补字段。 docsExamined ≫ nReturned 说明索引定位没问题但回表成了瓶颈。如果是高频固定字段查询,做覆盖查询把返回字段纳入索引;如果是选择性差(字段值太集中),考虑换查询条件或加辅助过滤字段。
分支四:内存 SORT → 排序纳入索引。 看到 SORT stage 就把排序字段按 ESR 纳入复合索引,让索引天然有序,省掉内存排序。尤其大结果集,不解决会直接报错。
分支五:结果集太大 → limit + 投影。 查询本身不慢,但返回了过多字段或过多文档,慢在网络传输和应用反序列化。加 limit、用投影只返回必要字段,往往比调索引更直接。
别忽略的几个非索引因素
慢查询不一定都是索引问题,几个常被忽略的因素:
数据不在 Cache。 WiredTiger 的 Cache 有限,冷数据要读盘。一个查询平时快、突然慢,往往是工作集超过了 Cache,索引再好也要读盘。这是存储引擎和内存问题,下一篇阶段三会展开。
连接池或网络。 driver 连接池太小,查询在客户端就排队;网络抖动让往返变长。这类问题看 mongostat 的连接数和应用侧的连接池监控,不在 explain 里体现。
锁竞争。 虽然 WiredTiger 是文档级锁,锁竞争比 MyISAM 那种表锁轻得多,但写密集场景仍可能出现。db.currentOp() 看等待中的操作。
统计信息过时。 优化器靠统计信息选计划,统计信息不准会选错计划。collMod 或自动统计更新机制能缓解。
验证与持续监控
改完索引或查询后,一定要再跑一次 explain 对比:totalDocsExamined、executionTimeMillis 是否真的下降了。凭感觉认为「应该快了」是不够的,explain 会说实话。
更重要的是把慢查询监控常态化:保持 Profiler Level 1、慢日志阈值合理、定期 review 慢查询 Top N。慢查询不是「优化一次就永久解决」的问题——数据量增长、查询模式变化、索引膨胀,都会让曾经的快查询重新变慢。闭环的意义就在这里:发现 → 定位 → 修复 → 验证 → 监控,循环往复。
判断框架
- 排查第一步是发现(Profile/慢日志),不是直接 explain 猜查询。
- 定位靠 explain 分层:COLLSCAN、扫描量比例、SORT、耗时分布。
- 五条修复分支:建索引、调顺序、覆盖查询、排序入索引、limit+投影。
- 非索引因素要排查:Cache、连接池、网络、锁、统计信息。
- 改完必须 explain 验证,凭感觉不算数。
- 慢查询监控常态化,这是持续工程,不是一次性任务。
这一篇是索引与查询优化阶段的实战收尾。下一篇会把整个阶段的机制收束成一张地图。
关于十三Tech
我是十三,All in AI Agent 方向的架构师,专注 AI 工程实践。
我相信 AI 是程序员的最佳搭档,也希望帮助每一位开发者更好地驾驭 AI。
如果你想继续跟完这套「图解 MongoDB」,欢迎关注公众号 「十三Tech」。后续会按存储引擎、高可用和分片集群这条线更新。

