explain不是万能执行计划查看器,仅反映查询阶段索引使用与扫描行为,对写操作只展示预估影响范围;find查询应链式调用explain("executionStats")获取真实执行细节,重点关注nReturned、totalDocsExamined和executionTimeMillis三字段。

explain 不是万能的执行计划查看器,它只反映查询阶段的索引使用和扫描行为,对写操作(insert/update/delete)仅展示预估影响范围,不展示实际写入路径或锁行为。
如何对 find 查询调用 explain 获取真实执行细节
直接在 find() 后链式调用 explain(),推荐用 "executionStats" 模式获取量化数据:
db.orders.find({ status: "shipped", createdAt: { $gt: ISODate("2024-01-01") } })
.explain("executionStats")
关键看三个字段:executionStats.nReturned(返回文档数)、executionStats.totalDocsExamined(扫描文档数)、executionStats.executionTimeMillis(毫秒级耗时)。若后者远大于前者 × 0.1,大概率存在索引缺失或低效索引。
- 避免用
"queryPlanner"模式——它不返回实际扫描量,容易误判“快” - 聚合管道需在
$match阶段前加explain,否则只分析最后阶段 - 带
.sort()的查询必须确保排序字段在索引中——否则executionStats.totalKeysExamined会飙升,且出现stage: "SORT"
update 和 delete 的 explain 只显示“打算怎么做”,不是“实际做了什么”
updateOne()、deleteMany() 等写操作的 explain() 输出中,executionStats 下的 nReturned 和 totalDocsExamined 是预估值,不等于最终修改/删除数量。MongoDB 在执行前只做查询计划评估,不模拟写路径。
-
updateOne({ x: 1 }, { $set: { y: 2 } }).explain()中的totalDocsExamined是匹配{ x: 1 }的文档数,不是实际更新数 - 如果 update 含数组操作符(如
$push),explain完全不体现数组字段的索引是否命中——得靠db.collection.getIndexes()手动核对 - 带
upsert: true的 update,explain不区分“更新”还是“插入”分支,无法预判是否触发全表扫描插入
为什么 aggregate 的 explain 结果常比 find 更难读
聚合管道每个阶段都可能独立生成执行计划,explain("executionStats") 返回的是整个管道的汇总统计,但 stages 数组里嵌套了各阶段的 stage 类型和子计划。重点盯住 stage: "IXSCAN" 是否出现在早期 $match 后,以及是否存在 stage: "COLLSCAN" 或 stage: "SORT"。
- 管道开头没
$match或$match条件太弱(如只用非索引字段),会导致后续所有阶段基于全集合运行 -
$lookup阶段的explain不显示被关联集合的索引使用情况——需单独对被查集合的find()调用explain - 使用
$facet时,explain会为每个子管道分别输出stages,但顶层executionStats是总和,易误读单个分支开销
真正卡顿的写操作,explain 往往给不出答案;它擅长暴露“查得慢”,但不解释“写得慢”。遇到高延迟更新,优先查 currentOp 和慢日志,而不是反复跑 explain。











