分片键查询仍扫描大量文档,因分片键不自动建索引、含$ne/$regex等操作符导致索引失效,或mongos无法精确下推条件而广播扫描;覆盖索引需满足:查询字段+投影字段全在单个索引中、符合esr顺序、无$text/$where等强制加载操作。

直接看 executionStats.totalDocsExamined 和 executionStats.totalKeysExamined:如果前者远大于后者(比如 10 倍以上),说明索引没被有效利用,或者压根没走索引;分片键查询尤其容易因字段顺序或投影不匹配导致覆盖失败。
为什么分片键查询还扫大量文档?
分片键本身不自动建索引,必须显式创建。即使建了,若查询中用了 $ne、$not、$regex(非前缀)等操作符,MongoDB 很可能放弃索引走 COLLSCAN。另外,分片集群中路由节点(mongos)若无法精确下推查询条件到目标分片,也会触发广播式扫描。
- 检查分片键是否已建索引:
db.collection.getIndexes()看是否存在{ shardKeyField: 1 }或复合索引以它开头 - 确认查询是否含“索引拒绝型”操作符:如
{ status: { $ne: "done" } }几乎必然触发全分片扫描 - 用
.explain("executionStats")查看shards字段下的每个分片执行情况,确认是否所有分片都只查了局部数据
覆盖索引必须满足的三个硬条件
只有同时满足这三点,MongoDB 才会跳过文档读取,纯靠索引返回结果——这对分片键查询提速最明显:
- 查询条件中所有字段,必须全部包含在同一个索引里(且符合 ESR 顺序:等值 → 排序 → 范围)
- 投影(
.find({}, { field1: 1, field2: 1, _id: 0 }))中列出的所有字段,也必须全部在该索引中 - 不能有
$text、$where、$expr等强制加载文档的操作
例如分片键是 region,常用查询是 db.orders.find({ region: "us-east", status: "shipped" }, { orderNo: 1, amount: 1, _id: 0 }),那索引必须是 db.orders.createIndex({ region: 1, status: 1, orderNo: 1, amount: 1 }) —— 少一个字段,就掉出覆盖范围。
createIndex 在分片集合上的关键参数陷阱
在分片集合上建索引,background: true 不仅无效,还会让 mongos 报错;6.0+ 默认要求前台构建(background: false),否则阻塞写入。更隐蔽的问题是:如果索引字段包含数组,而分片键又不是该数组字段,MongoDB 可能拒绝创建(报 cannot index parallel array)。
- 务必在 mongos 上执行
createIndex,而非直连某个 shard;否则索引只存在于单个分片,查询时不可见 - 避免在分片键上建稀疏索引(
sparse: true):分片逻辑依赖分片键存在性,稀疏索引会导致路由失效 - 复合索引首字段必须是分片键或其前缀,否则在分片环境下无法高效定位数据块(chunk)
验证覆盖是否生效的最小检查链
别只信 explain 输出里的 indexOnly: true(6.0 已弃用),重点盯这三个字段:
-
executionStats.executionStages.stage === "IXSCAN"(不是COLLSCAN或FETCH) -
executionStats.totalDocsExamined === 0(真正没读文档) -
executionStats.nReturned === executionStats.totalKeysExamined(每条返回都来自索引键,无过滤损耗)
如果 totalDocsExamined > 0,哪怕只有 1,说明仍有字段没进索引,或用了不支持覆盖的操作符。这时候加字段比调优查询语句更直接有效。











