collscan 更危险,因其表示全集合扫描且未使用索引,导致性能严重下降;ixscan 虽用索引但未必高效,需结合 fetch、sort、indexbounds 等判断实际效率。

COLLSCAN 和 IXSCAN 哪个更危险?
COLLSCAN 表示全集合扫描,意味着 MongoDB 没有使用索引,而是逐条读取整个集合来匹配条件。只要 executionStats.nReturned 远小于 executionStats.totalDocsExamined,基本可以判定存在索引缺失或失效问题。
IXSCAN 是索引扫描,但不等于“高效”——它只说明用了索引,不代表覆盖了查询。如果后续还出现 FETCH 阶段,说明 MongoDB 仍需回表查完整文档;若只有 IXSCAN + PROJECTION,才可能是覆盖查询(covered query)。
- IXSCAN 的
indexName字段必须和你预期的索引一致,否则可能是选错了索引 - 注意
executionStats.executionStages.inputStage.indexBounds,它显示实际使用的索引范围;如果 bounds 是{ "year": ["MinKey", "MaxKey"] },说明索引字段没被有效过滤 - 复合索引中,
$in只能用在最后一个字段上,否则前面字段的范围扫描会中断(例如{ year: 1, rated: 1 }上查{ year: { $gt: 1990 }, rated: { $in: ["PG"] } }是 OK 的;但反过来就不行)
FETCH 阶段为什么总在 IXSCAN 后面?
FETCH 出现,代表 MongoDB 找到了匹配的索引条目,但还需要根据 _id 去磁盘/内存里捞出完整文档。这本身不是错误,但它是性能损耗点——尤其当 nReturned 小、totalDocsExamined 大时,说明索引筛选效率低,或者投影字段太多导致无法覆盖。
一款AI工具,主要用于将编码任务调度到本地 OpenAI Codex CLI,支持后台执行、状态轮询以及可交互式回答的澄清问题。适用于 OpenClaw 需要……,适合需要提升相关任务效率的用户。
- 想消除 FETCH,得让查询只涉及索引字段(包括
_id),且用.project()显式限定返回字段 - 如果业务允许,把常用查询字段加进复合索引末尾(如
{ year: 1, rated: 1, title: 1, _id: 1 }),就能支持只走索引返回数据 - MongoDB 不会自动用 _id 索引优化非 _id 查询,所以别指望默认 _id 索引能帮上忙
SORT 阶段出现在 explain 里意味着什么?
SORT 阶段表示排序操作发生在内存中,而非靠索引顺序完成。一旦看到它,基本可以确认:查询的 sort 字段没包含在索引前缀中,或索引方向(asc/desc)与 sort 不一致。
- 比如
.sort({ createdAt: -1 }),但索引是{ createdAt: 1 },就无法复用,会触发 SORT - 内存排序受
maxSortMemoryUsageBytes限制,超限会报错Sort exceeded memory limit - 分页场景下,
.skip().limit()配合 SORT 尤其危险——它先排序全部结果再跳过,而不是跳过再排序
winningPlan 里出现 express_XXX 是好事还是坏事?
express_XXX(如 EXPRESS_IXSCAN)是 MongoDB 8.0+ 引入的快速路径阶段,绕过常规查询规划器,直接走预编译索引扫描逻辑。它只对极简查询生效(例如单字段等值 + 单字段排序),属于性能红利,但不可控也不可依赖。
- 它不会出现在旧版本 explain 输出中,也不是所有等值查询都能触发
- 一旦查询加了
$or、正则、$text或任意聚合阶段,express 路径立即失效,回落到普通 winningPlan - 别为它调优——它只是锦上添花;真正要盯的,还是 IXSCAN 的 bounds、FETCH 的存在与否、以及 SORT 是否可避免
最常被忽略的一点:explain 默认不缓存计划,且禁止当前查询写入计划缓存。这意味着你看到的执行路径,未必是应用真实跑起来时走的那条——尤其在有参数化查询、字段类型隐式转换(比如字符串数字 vs 数字)的场景下,缓存中的计划可能完全不同。










