因为$slice需解析原始bson结构截取数组,强制触发文档fetch,无法仅靠索引条目返回结果;真正能走索引覆盖的是投影字段全在索引中且不含任何文档级操作的查询,如db.orders.find({status:"shipped"},{status:1,updated_at:1,user_id:1,_id:1})配合对应复合索引。

为什么MongoDB数组切片$slice查询无法走索引覆盖
当你在投影中使用 {$slice: 5} 控制数组返回长度时,MongoDB 会强制加载完整文档并解析整个数组字段,哪怕其他所有投影字段都已包含在复合索引中——这是因为 【$slice 是文档级操作,必须触达原始 BSON 结构才能截取子序列】,它天然绕过索引覆盖机制。
真正能走索引覆盖的查询长什么样
索引覆盖要求 MongoDB 完全不读取磁盘上的文档数据,只靠索引条目就能组装出结果。这只有在 projection 中的每个字段(包括 _id)都显式出现在索引定义里,且不涉及任何需解析文档体的操作时才成立。
db.orders.find({status: "shipped"}, {status: 1, updated_at: 1, user_id: 1, _id: 1}) → 若索引为 {status: 1, updated_at: -1, user_id: 1, _id: 1},explain 显示 "totalDocsExamined": 0,即成功覆盖。
一旦你在 projection 里加入 {logs: {$slice: 5}},哪怕 logs 字段根本没建索引,MongoDB 也必须 FETCH 整个文档:因为 【WiredTiger 需解压 BSON、定位 logs 字段偏移、按字节跳转到数组起始位置、再逐元素计数截取】,这个过程完全脱离索引键空间。
验证是否被 $slice 破坏覆盖的三步法
第一步:执行带 $slice 的查询并加 explain("executionStats")
第二步:检查 executionStats.executionStages.stage 是否为 IXSCAN → 是,说明索引被用于过滤;但若同时看到 "totalDocsExamined" > 0,就证明 FETCH 已介入
第三步:对比去掉 $slice 后的 same query,观察 totalDocsExamined 是否降为 0 —— 如果降了,说明 $slice 就是那个打破覆盖的临界点











