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,回表文档数依然很大;此时应评估复合索引的字段顺序,而不是简单地再加一个索引。