← 中文资源

简体中文版本

MongoDB — explain() 揭示热查询走 COLLSCAN

修复做全集合扫描的 MongoDB 查询,读懂 explain() 阶段树,按 ESR 顺序建复合索引,并覆盖查询以消除 FETCH。

阅读英文原文

症状

  executionStats 用数字讲故事:nReturned 50、docsExamined 421 万、totalKeysExamined 0——选择性差距就是全部成本,零键检查证明没有索引参与。COLLSCAN 意味着每次检查都要反序列化文档,成本随集合大小线性增长,与谓词选择性和 limit 无关。修好访问路径后还要会读形状:IXSCAN 下挂着 SCAN_AND_SORT 说明排序在内存里跑(阻塞式、32MB 上限),因为索引没承载排序顺序。

诊断思路

  先读 winning plan 的阶段树与 executionStats 的 docsExamined/nReturned 差距;再对照 getIndexes() 列出的字段顺序与查询形状——等值、排序、范围的 ESR 顺序是否吻合;最后按正确顺序重建索引并用 explain 验证 docsExamined 坍缩到接近 nReturned。前缀不匹配(前导字段与等值谓词不符)、索引服务不了的运算符(无前缀的不区分大小写正则、$where)、复合索引字段顺序错误,是三大入口。

根因

  没有前导字段服务于查询谓词的索引时,MongoDB 必须检查集合里的每个文档来应用过滤。每次检查都反序列化文档,成本随集合大小线性增长。

修复与验证

  按 ESR 顺序为该查询形状建复合索引:等值(customerId、status)→ 排序(createdAt)→ 范围;投影只取索引内字段实现 PROJECTION_COVERED、彻底消除 FETCH;为 profiler 实际观察到的每个热形状建索引,删除与他人前缀重复的索引。验证时重跑 explain:IXSCAN 无阻塞排序、docsExamined 在 nReturned 的小倍数内、profiler 不再标记该查询。每个索引都在向写入与存储收税——为观测到的形状建,不为想象中的建。

代码示例

> db.orders.find({customerId: 42, status: "paid"})
    .sort({createdAt: -1}).limit(50).explain("executionStats")

winningPlan: { stage: "COLLSCAN", filter: {...}, direction: "forward" }
executionStats: { nReturned: 50, docsExamined: 4210773, totalKeysExamined: 0 }
// Equality (customerId, status) -> Sort (createdAt) -> Range (none)
db.orders.createIndex({ customerId: 1, status: 1, createdAt: -1 })
db.orders.find(
  { customerId: 42, status: "paid" },
  { _id: 0, createdAt: 1, total: 1 }   // fields present in the index
).sort({ createdAt: -1 })
// plan: PROJECTION_COVERED over IXSCAN — zero document fetches