foreignfield 必须手动建索引,否则 $lookup 会全表扫描;类型必须严格一致(如 objectid 而非字符串),否则匹配失败却无报错;分片集群下 foreignfield 还需是分片键或其前缀,否则索引失效。

右表 foreignField 必须建索引,否则全表扫描
不建索引的 foreignField 会让 $lookup 在每次匹配时遍历整个右集合,数据量稍大(比如超 10 万文档)就明显卡顿。MongoDB 不会自动为 foreignField 创建索引,必须手动加。
- 用
db.otherCollection.getIndexes()检查是否已有索引,重点看foreignField是否在某个索引的key中 - 没有就立刻建:
db.otherCollection.createIndex({foreignField: 1}),升序即可,$lookup等值匹配不依赖方向 - 如果
foreignField常和其它字段一起被$match过滤(比如{status: "active", foreignField: ObjectId(...)}),考虑建复合索引:db.otherCollection.createIndex({status: 1, foreignField: 1})
localField 也要索引?多数情况不用
localField 所在集合通常是主查询入口(比如先 $match 过用户列表),它的索引由上层查询驱动,不是 $lookup 性能瓶颈。但有两个例外:
- 如果聚合开头没
$match,而是直接$lookup,且localField有高基数(如唯一 ID),建索引可加速左表文档遍历 - 如果
localField是表达式结果(如$toString: "$_id"),MongoDB 5.0+ 才支持索引利用,旧版本即使建了也无效——此时应改用原始字段类型匹配
索引建了还是慢?检查字段类型是否严格一致
$lookup 的等值匹配对类型敏感:一边是 ObjectId,另一边是字符串形式的 ID(如 "60a1b2c3d4e5f67890123456"),匹配必然失败,结果数组为空,但 MongoDB 不报错,只会默默返回空 as 数组。
- 用
db.leftCollection.findOne().localField和db.rightCollection.findOne().foreignField分别看值和类型 - 在 shell 中验证:
typeof db.leftCollection.findOne().localFieldvstypeof db.rightCollection.findOne().foreignField - 类型不一致时,要么统一转成
ObjectId(推荐),要么在$lookup的pipeline中用$convert强制转换(仅 4.0+ 支持,有性能损耗)
分片集群下 foreignField 索引可能失效
在分片集群中,$lookup 跨分片关联受限。即使 foreignField 有索引,若该字段**不是分片键或分片键前缀**,MongoDB 无法将查询路由到目标分片,会退化为广播查询,性能骤降甚至超时。
- 确认右集合分片键:
sh.status()查看shard key字段 - 如果
foreignField不是分片键,且业务允许,优先将其设为分片键;否则改用应用层双查(先查左表,再按 ID 批量查右表)更可控 - MongoDB 4.2+ 支持有限跨分片
$lookup,但要求from集合与当前集合使用相同分片键——这点容易被忽略,配置不对就等于没索引











