explain("executionStats")里没看到IXSCAN,说明索引根本没被选中,执行计划压根没走索引路径;常见原因包括查询字段未建索引、字段类型不匹配、复合索引顺序错误、$match未置顶或混用$expr等。

explain("executionStats")里没看到IXSCAN,说明索引根本没被选中
不是“没显示”,而是执行计划压根没走索引路径——IXSCAN阶段缺失,大概率是COLLSCAN或FETCH直接出现在顶层inputStage里。这和查询写法、索引定义、字段类型三者是否对齐强相关。
常见诱因包括:
-
$match条件字段没建索引,或建了但类型不匹配(比如索引是字符串,查询传的是数字) - 复合索引顺序错误:查询用了
{age: {$gt: 25}, name: "Alice"},但索引是{name: 1, age: 1}——age是范围查询,它后面的name字段无法命中索引前缀 - 聚合管道里
$match没放在最前面,比如[$sort, $match, $limit],优化器无法提前剪枝,只能全扫再过滤 - 查询中混用了
$expr,例如{$expr: {$in: ["$status", ["active", "pending"]]}},这种表达式强制绕过普通索引路径
为什么explain()默认模式下“看着像走了索引”,实际却没用上?
explain()不带参数时默认是queryPlanner模式,它只模拟计划、不真正执行,返回的winningPlan可能显示IXSCAN,但那是理想推演,不是真实行为。
必须用.explain("executionStats")——它会真实跑一次管道,返回executionStats里的totalDocsExamined和totalKeysExamined才是铁证:
- 若
totalKeysExamined === 0,说明索引完全没参与 - 若
totalDocsExamined远大于nReturned(比如查10条却扫了50万文档),说明索引只做了部分过滤,后续仍大量回表 - 注意:即使有
IXSCAN节点,也要看它在stages树中的位置;如果它嵌套在FETCH之下,说明只是用来定位文档ID,不是直接输出结果
聚合中$sort没走索引,但explain里有IXSCAN,问题出在哪?
IXSCAN存在 ≠ 排序走索引。关键看IXSCAN之后有没有SORT阶段,以及SORT的works值是否为0。
典型失效场景:
- 索引字段没包含
$sort字段,比如查{status: "shipped"}并sort({createdAt: -1}),但索引只有{status: 1}——IXSCAN只用于过滤,排序仍内存完成 - 复合索引顺序不对:
{status: 1, createdAt: -1}能支撑$match + $sort,但{createdAt: -1, status: 1}不行,因为status等值条件无法跳转到有序的createdAt片段 -
$sort在$match之后但中间插了$project或$unwind,导致数据流中断,索引排序能力被切断
验证索引是否真生效的最小可执行命令
别猜,直接跑这条(替换集合名和条件):
db.orders.explain("executionStats").aggregate([
{ $match: { status: "shipped", createdAt: { $gte: ISODate("2026-01-01") } } },
{ $sort: { createdAt: -1 } },
{ $limit: 20 }
])
盯住返回里的三个地方:
-
executionStats.totalKeysExamined> 0? -
executionStats.executionStages.stage第一层是不是"IXSCAN"? - 展开
executionStages.inputStage,里面有没有"stage": "SORT"?如果有,它的"works"是不是远大于0?
只要其中任一条件不满足,“未显示走索引”就不是explain的bug,而是查询与索引之间存在真实错配。最常被忽略的是:类型一致性和$match必须为管道第一个stage这两点,其他都容易调,这两点一旦出错,整个优化路径就偏了。











