覆盖查询要求filter和projection字段全部包含在同一索引中且显式排除_id,否则触发FETCH;验证需满足explain中totalDocsExamined=0且stage为IXSCAN或PROJECTION_COVERED。

能,但必须严格满足条件:查询的 filter 和 projection 字段全部落在同一个索引里,且不触发任何需要文档内容的操作。
为什么 find() 有时还是读了文档?
覆盖查询不是“加个索引就自动生效”,而是 MongoDB 在执行时发现所有需要的数据(过滤字段 + 返回字段)都在索引节点里,才跳过文档加载。一旦有任一字段不在索引中,或隐式要求了 _id(而索引没包含它),就会回表——即进入 FETCH 阶段,读取完整文档。
-
_id默认总是返回,除非显式写{_id: 0};但绝大多数复合索引都不含_id,所以漏掉这句就破功 - 正则、
$ne、$not、$exists等操作可能让索引失效,导致无法覆盖 - 投影里多写一个字段(比如索引是
{a: 1, b: 1},却查{a: 1, b: 1, c: 1}),哪怕c是null也会强制 FETCH
怎么验证是否真的覆盖了?
只看 explain("executionStats") 的两个硬指标:
-
executionStats.totalDocsExamined必须为0 -
executionStats.executionStages.stage必须是IXSCAN或PROJECTION_COVERED,不能出现FETCH
别信 extraInfo.covered(旧版驱动才有,新版已移除),也别只看 stage === "IXSCAN" 就认为覆盖了——如果后面嵌套了 FETCH,那还是回表。
聚合管道里还能用覆盖吗?
能,但范围极窄:仅 $match + $project(且投影字段全在索引中)可覆盖;其他阶段一律破功。
-
$lookup、$unwind、$group、$sort都会强制加载文档 - 即使
$sort字段已建索引,只要出现在 pipeline 中,就不再算覆盖查询 - 聚合里
_id同样默认返回,$project中必须写_id: 0才安全
例如:db.orders.aggregate([{$match: {status: "paid"}}, {$project: {orderId: 1, amount: 1, _id: 0}}]) 只有在索引是 {status: 1, orderId: 1, amount: 1} 时才可能覆盖。
复合索引字段顺序怎么排?
顺序决定能不能同时支撑过滤、排序、投影——不是“字段齐全就行”。
- 等值字段(
{a: "x", b: "y"})放最前 - 范围字段(
{c: {$gt: 10}})放中间 - 投影字段(
d,e)放最后,且必须全部包含
比如索引 {status: 1, createdAt: -1, name: 1},可以覆盖 find({status: "active"}, {status: 1, name: 1, _id: 0});但如果投影里加了 createdAt,而查询条件没用它,MongoDB 仍可能走 IXSCAN,但得看实际执行计划——顺序错,即使字段全,也可能无法覆盖。
真正难的不是建索引,是让每个查询语句和索引结构严丝合缝地对齐。少一个 _id: 0,多一个未索引字段,或者正则写在等值字段后面,都足以让覆盖失效。


















