首页/数据/NoSQL 速查/explain 解读查询计划
MongoDB99 条

explain 解读查询计划

explain("executionStats") 展示索引选择与执行统计:重点看 winningPlan 的阶段(IXSCAN/COLLSCAN)以及扫描文档数与返回数之比。

NoSQL 速查数据更新于 2026-10-13

explain 是什么

explain 展示查询的执行计划:优化器选了哪个索引、扫描了多少索引键与文档、各阶段的耗时与返回条数。它回答的是「这条查询到底走没走索引、代价花在哪」。find 用 .explain("executionStats"),聚合用 .explain("executionStats") 或 { explain: true }。判断重点不是看到 IXSCAN 就放心,而是比较扫描量与返回量。

基本信息

语法db.collection.find(...).explain("executionStats")
模式queryPlanner / executionStats / allPlansExecution
关键字段winningPlan.stage、totalKeysExamined、totalDocsExamined、nReturned
判断标准docsExamined 接近 nReturned 说明过滤高效
聚合用法aggregate(pipeline).explain("executionStats")
适用版本MongoDB 4.x 及以上字段含义稳定

典型使用场景

  • 慢查询定位:确认是否走了 COLLSCAN 全集合扫描
  • 索引验证:新建索引后确认查询确实切换到了该索引
  • 索引取舍:比较不同索引下的扫描量与耗时
  • 聚合调优:确认 $match 是否被下推到索引扫描阶段
  • 分页优化:检查深分页条件是否仍是逐文档扫描
  • 上线前评审:核心查询计划留档,作为性能基线

示例

示例
db.orders.find({ status: "paid" }).explain("executionStats")                      # 完整统计
const p = db.orders.find({ status: "paid" }).explain()                            # 只看计划,不执行
printjson(p.queryPlanner.winningPlan.stage)                                        # 顶层阶段
const s = p.executionStats; print(s.nReturned + " / " + s.totalDocsExamined)       # 返回量比扫描量
db.orders.aggregate([{ $match: { status: "paid" } }], { explain: true })           # 聚合版
db.orders.find({ sku: "A-1" }).hint({ sku: 1 }).explain("executionStats")          # 强制指定索引对比

关键字段读法

字段含义判断标准
winningPlan.stage最终选中的计划阶段IXSCAN 好、COLLSCAN 差
totalKeysExamined扫描的索引键数越接近返回数越好
totalDocsExamined回表读取的文档数远大于返回数说明索引选择性差
nReturned实际返回文档数与扫描量配合看效率
executionTimeMillis执行耗时受缓存影响,作参考
rejectedPlans被放弃的计划对比可发现更优索引
聚合用 { explain: true } 选项或在管道后调用 .explain("executionStats"),重点确认 $match 是否被优化器推到了索引扫描,而不是先全扫再过滤。
allPlansExecution 模式会给出所有候选计划的统计,适合排查「为什么优化器没选那个看起来更好的索引」这类问题。
executionStats 只反映当前这一次执行:缓存冷热会显著影响耗时数字,横向比较要以扫描量为准,不要只盯毫秒数。
复合索引的字段顺序直接决定可用的索引前缀:查询只用到第二个字段时通常无法有效利用该索引,看计划的 indexBounds 能确认裁剪范围。

常见计划阶段含义

  • COLLSCAN:全集合扫描,集合大时必须优先处理
  • IXSCAN:索引扫描,仍需回表文档数才能判断效率
  • FETCH:按索引结果回表取文档,回表量大说明索引不够覆盖
  • SORT:内存排序,数据量大时可能触发内存上限报错
  • LIMIT / SKIP:分页阶段,深分页的 skip 代价最高
  • PROJECTION:投影阶段,常与 FETCH 合并执行

优化动作清单

  • 给过滤字段建索引,等值条件字段排在复合索引前面
  • 尽量构成覆盖查询:只返回索引里已有的字段
  • 深分页改成基于上一页最后一条记录的范围翻页
  • 用 hint 对比不同索引的计划,确认最优选择
  • 清理从未被使用的索引,减少写入放大
  • 核心查询的计划留档,变更后对比扫描量有无恶化
计划里的 indexBounds 反映索引被裁剪的范围:范围条件过宽说明索引选择性有限,需要重新评估复合索引的字段顺序。
queryPlanner 模式只给出计划不执行查询,适合上线前静态评审;要拿到扫描量等统计必须用 executionStats 模式。
索引选择性差时即使走了 IXSCAN,回表文档数依然很大;此时应评估复合索引的字段顺序,而不是简单地再加一个索引。

易错点

  • 看到 IXSCAN 就认为高效:totalDocsExamined 远大于 nReturned 时索引选择性依然很差
  • 只看毫秒数做优化判断,缓存冷热会让同一查询的耗时相差数倍,应以扫描量为准
  • 在热数据上执行 executionStats 会带来额外开销,生产环境避免对超大集合频繁执行
  • $match 没被推入索引扫描时聚合会先读取大量文档,计划里能看到这一点但常被忽略
  • 索引建了但查询条件类型不匹配(字符串与数值混用)时索引无法生效,计划仍显示 COLLSCAN

数据来源与参考

  • MongoDB Manual: explain Results
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌