必须显式指定 hint() 才能触发 MongoDB 8.0 新查询引擎的索引路径选择,因其默认仅在满足特定条件时启用,不加 hint() 可能因统计过期或谓词选择性低而退回到旧执行器。

用 find() 时必须显式指定 hint() 才能触发新查询引擎的索引路径选择
MongoDB 8.0 的新型查询引擎(Query Engine V2)默认只在满足特定条件时启用:它不会自动接管所有 find() 查询,而是依赖优化器对查询形状、索引可用性和统计信息的综合判断。最直接、最可控的激活方式,是在有明确索引预期的查询中加上 hint()。比如你有一个复合索引 { status: 1, updatedAt: -1 },但发现 db.orders.find({ status: "shipped" }).sort({ updatedAt: -1 }) 仍走 collection scan,那就得强制:db.orders.find({ status: "shipped" }).sort({ updatedAt: -1 }).hint({ status: 1, updatedAt: -1 })。不加 hint(),引擎可能因统计过期或谓词选择性低而退回到旧执行器。
bulkWrite() 中混用 ReplaceOneModel 和 UpdateOneModel 会绕过新引擎的批量计划优化
新引擎对批量写入做了深度 pipeline 合并优化,但前提是操作类型一致且语义可合并。一旦你在同一个 bulkWrite() 调用里同时塞进 ReplaceOneModel(全量替换)和 UpdateOneModel(字段级更新),MongoDB 会降级为逐条执行——既失去向量化写入收益,也无法利用新引擎的预解析缓存。实际生产中常见于“先查后更”的误用模式:比如先 find() 拿出文档再构造 ReplaceOneModel,其实完全可以用 UpdateOneModel + $set + $inc 完成原子更新。真正需要 ReplaceOneModel 的场景极少,仅限文档结构彻底重构。
聚合管道里 $match 放错位置会让新引擎无法下推索引过滤
新引擎能将部分 $match 阶段下推到存储层做 early filtering,但前提是它出现在管道最前端,且不依赖前序阶段输出。例如:[ { $match: { type: "order", createdAt: { $gte: ISODate("2026-01-01") } } }, { $lookup: ... }, { $match: { "items.0": { $exists: true } } } ] —— 第二个 $match 因依赖 $lookup 结果,无法下推;而第一个可以。如果把第一个 $match 写成 { $expr: { $and: [ { $eq: ["$type", "order"] }, ... ] } },也会失效:新引擎目前只下推传统 BSON 查询语法({ type: "order" }),不支持 $expr 中的表达式树。这点在迁移到 8.0 后极易被忽略。
explain("executionStats") 输出里出现 "queryPlanner.winningPlan.queryPlan" 字段才代表真正在用新引擎
别只看 "executionTimeMillis" 变快了就以为生效了。MongoDB 8.0 的 explain 默认仍返回旧格式,必须显式传参 "executionStats" 或 "allPlansExecution",然后检查返回 JSON 中是否存在 queryPlanner.winningPlan.queryPlan 这一嵌套结构——这是新引擎执行计划的唯一标识。如果只有 winningPlan.stage === "COLLSCAN" 或 "IXSCAN" 顶层字段,说明还在用 legacy query planner。另外注意:带事务的查询、使用 $text 或 $geoNear 的查询,目前仍不进入新引擎路径,这点文档没明说但实测如此。











