mongodb中ixscan后出现fetch说明未走覆盖索引,根本原因是_id默认被返回但未包含在索引中;必须显式排除_id:0且索引包含所有投影、查询及排序字段,再通过explain("executionstats")确认iscovered为true。

为什么只查索引字段却仍出现 FETCH 阶段
MongoDB 的 IXSCAN 后跟着 FETCH,说明查询没走“覆盖索引”(covered query)。哪怕你只查了索引里有的字段,只要 _id 被隐式包含,就可能触发 FETCH——因为默认所有文档都带 _id,而它不一定在你的索引里。
常见错误现象:explain("executionStats") 显示 executionStages.stage === "FETCH",即使 projection 里只写了索引字段。
- 默认查询总会返回
_id,除非你显式排除:{ _id: 0 } - 你的索引必须包含
_id字段(或你主动排除它),否则 MongoDB 得回表取_id值 - 复合索引顺序不重要,但字段必须完整覆盖投影 + 查询条件 + 排序字段
如何确认是否真正走了覆盖索引
关键看 explain() 输出里的 executionStages.isCovered 是否为 true,而不是只盯 IXSCAN。它才是 MongoDB 官方定义的“覆盖索引”判定依据。
使用场景:调试慢查询、压测高并发读、验证索引有效性。
-
db.collection.find({ a: 1 }, { a: 1, b: 1 }).explain("executionStats")→ 检查isCovered - 如果
isCovered === false,再查executionStages.stage看是不是FETCH - 注意:MongoDB 5.0+ 在
explain("queryPlanner")里不显示isCovered,必须用"executionStats"
为什么排除 _id 后还可能有 FETCH
排除 _id 是必要但不充分条件。还有几个隐蔽坑:
- 索引没建全:比如查
{ a: 1, b: 1 },但索引只有{ a: 1 }→ 必须重建复合索引 - 用了
$text、$regex、$where等无法被索引完全支撑的操作 → 强制 FETCH - 排序字段不在索引中:如
.sort({ c: 1 })但索引不含c→ 即使投影全索引,也会 FETCH 后内存排序 - 聚合管道里用了
$project但字段来源不是索引(例如计算字段、$concat)→ 索引失效
实操:三步写出真正覆盖的查询
别靠猜,按顺序检查:
- 第一步:确保索引包含所有用到的字段(含查询、投影、排序)
db.collection.createIndex({ a: 1, b: 1, c: 1 }) - 第二步:投影显式排除
_id:{ a: 1, b: 1, _id: 0 }(不能省略_id: 0) - 第三步:验证执行计划:
db.collection.find({ a: 1 }, { a: 1, b: 1, _id: 0 }).explain("executionStats")→ 看isCovered === true且无FETCH
容易被忽略的是:哪怕你只查一个字段,只要没写 _id: 0,MongoDB 就默认要它;而多数人建索引时根本没把 _id 加进去——毕竟它默认主键,但覆盖索引要求“所有返回字段都在索引里”,_id 也不例外。










