索引是否命中直接看executionStats.executionStages.stage字段:IXSCAN表示走了索引,COLLSCAN为全表扫描;再对比nReturned与totalDocsExamined,若相差过大说明索引未高效利用。

explain 输出里怎么看索引是否命中
直接看 executionStats.executionStages.stage 字段:如果是 IXSCAN,说明走了索引;如果是 COLLSCAN,就是全表扫描——性能杀手,必须优化。
更关键的是检查 executionStats.nReturned 和 executionStats.totalDocsExamined:如果两者相差很大(比如返回10条但扫了50万文档),说明索引没覆盖查询条件或排序字段,可能触发了 FETCH 或 SORT 阶段。
explain 的三种模式怎么选
日常排查用 executionStats 模式就够了,它给出真实执行耗时和扫描量;queryPlanner 只做计划预估,不运行查询,适合快速验证索引结构是否被识别;allPlansExecution 会强制运行所有候选计划并对比,只在怀疑 MongoDB 选错执行计划时才用——代价高,别在生产环境随便跑。
调用方式:db.collection.find({...}).explain("executionStats")
常见误判点:IXSCAN ≠ 高效
IXSCAN 只代表“用了索引”,不代表“用得好”。容易忽略的坑有:
- 索引字段顺序错:比如查询
{status: "active", created_at: {$gt: ISODate(...)}},却建了{created_at: 1, status: 1},那status就无法走索引前缀 - 范围查询后还有排序:
{a: 1, b: 1, c: -1}索引上,find({a: 1, b: {$gt: 10}}).sort({c: -1})能用,但find({a: 1, c: {$gt: 10}}).sort({b: 1})就不能——c是范围,b排序失效 - 投影字段没包含在索引里:查
{name: 1, email: 1}却只建了{name: 1},就会多一次 FETCH 去磁盘捞email
聚合管道里 explain 怎么看
聚合查询的 explain 结构更深,重点盯 stages 数组里的每个 $stage。特别注意:
-
$match阶段是否出现IXSCAN:它决定整个 pipeline 能否从源头过滤数据 -
$sort阶段有没有memUsage或spilled字段:有就说明内存不够,被迫写临时文件,性能断崖下跌 -
$lookup后是否带pipeline:内嵌 pipeline 如果没走索引,会放大性能问题
真正难调的不是单个查询,而是那些看起来“走了索引”、但 totalDocsExamined 远大于 nReturned 的 case——这时候得回过头去核对字段顺序、是否覆盖投影、有没有隐式类型转换。











