foreignField 必须单独建单字段索引,复合索引无效;localField 索引是前置加速前提;分片集群需各分片统一建索引;跨库或嵌套 $lookup 时索引须建在实际查询的目标集合上。

foreignField 必须单独建索引,复合索引无效
绝大多数人给 $lookup 加索引时,只在主集合(localField 所在集合)上建了索引,却漏掉了 from 集合的 foreignField。MongoDB 执行 $lookup 时,本质是对 from 集合做一次独立等值查询:用每条输入文档的 localField 值去查 foreignField。如果这个字段没索引,就会全表扫描。
常见错误是认为“我已在 products 上建了 {_id:1, category:1} 索引,查 _id 肯定快”——错。MongoDB 不会用复合索引中非首字段做等值匹配。哪怕你查的是 category,而索引是 {name:1, category:1},也完全不生效。
- 必须显式为
foreignField创建单字段索引,例如:db.products.createIndex({ "_id": 1 }) - 如果
foreignField是嵌套字段(如metadata.code),索引路径必须完整一致:db.products.createIndex({ "metadata.code": 1 }) - 分片集群下,该索引需在每个分片上存在,且类型一致(不能一个分片是哈希索引,另一个是 B-tree)
localField 索引不是可选的,而是前置加速前提
localField 所在集合的索引,作用不在 $lookup 阶段本身,而在它之前的 $match 或整个管道入口。但如果你的 $lookup 写在管道靠前位置(比如第二步),而第一步 $match 又没走索引,大量无关文档就会流入 $lookup,导致它被迫执行成千上万次无意义的关联查询。
- 典型错误写法:
db.orders.aggregate([{$project:{productId:1}}, {$lookup:{from:"products", localField:"productId", foreignField:"_id", as:"p"}}])——$project后无法利用productId上的索引过滤 - 正确顺序:先
$match(带索引字段),再$lookup,最后$project - 如果
localField是数组(如tags: ["a","b"]),MongoDB 会为每个元素分别执行一次$lookup,此时foreignField的索引压力翻倍,务必确认是否真需要这种语义
验证索引是否真正生效,别信 explain 里的 IXSCAN
很多人跑 explain("executionStats") 看到 “IXSCAN” 就以为索引生效了,其实那是对主集合(输入集合)的扫描,和 $lookup 是否走索引无关。真正要查的是 from 集合的执行计划。
- 必须单独对 from 集合执行等值查询并
explain,例如:db.products.find({ "_id": ObjectId("...") }).explain("executionStats"),确认其中executionStage是IXSCAN且nReturned≈ 1 - 聚合管道里
$lookup阶段的explain输出中,没有字段能直接告诉你 foreignField 是否用了索引;唯一可靠方式是模拟其行为单独测 from 集合 - 如果 from 集合是分片的,需在每个分片上分别验证索引是否存在且被使用
跨库或嵌套 $lookup 时索引容易被忽略
在 Atlas Data Federation 或支持跨库的部署中,from 写成 {db: "otherdb", coll: "events"} 后,foreignField 索引必须建在 otherdb.events 上,而不是当前库的同名集合。更隐蔽的是,如果第一层 $lookup 已指定跨库,其输出文档进入第二层 $lookup 时,若新 $lookup 没再声明 db,会自动沿用上一层的数据库上下文——这意味着第二层的 foreignField 索引也要建在那个目标库中。
- 错误写法:
from: "analytics.events"或from: "events"→ 实际查的是当前库,索引建错地方 - 自建 MongoDB 6.x 集群不支持跨库
$lookup,即使语法合法也会报错,此时应考虑数据同步或应用层 join - 嵌套
$lookup(比如 A→B→C)中,B 的foreignField和 C 的foreignField都需各自建单字段索引,且不能复用
$lookup 运行时实际发起的那条子查询,而不是你写的聚合语句表面看起来的样子。


















