$lookup性能差主因是索引缺失、顺序错误、类型不一致及全字段拉取,需为foreignField建索引、$match前置、确保类型严格一致、用pipeline裁剪字段。

$lookup 性能差,本质是数据搬运和匹配逻辑失控,不是语法问题。直接上索引、调顺序、压 pipeline,别等它慢了再查 explain。
被关联集合的 foreignField 必须建索引
这是最常漏掉、也最容易验证的一点。$lookup 不会自动用索引——你没显式建,它就全表扫描。比如 from: "orders",foreignField: "user_id",那 db.orders.createIndex({ user_id: 1 }) 就得有。
- 用
db.orders.getIndexes()确认索引是否存在,别靠猜 - 分片集群里,这个索引必须是分片键前缀,否则路由层没法下推过滤
- 索引方向(
1或-1)对等值查询没影响,但后续带$sort就得一起考虑
$match 必须放在 $lookup 前面
顺序错了,索引再好也白搭。主集合没提前过滤,$lookup 就得为每一条文档都发起一次子查询——哪怕每条只耗几毫秒,一万条就是几十秒。
- 错误写法:
[$lookup, { $match: { status: "active" } }]—— 此时$match在后,无法减少输入量 - 正确写法:
[{ $match: { status: "active" } }, $lookup],且status字段最好也已建索引 - 如果主集合平均关联上千条记录,管道极易触发
Exceeded memory limit for $group
用 pipeline 形式替代基础 $lookup
基础写法默认把整个匹配文档全拉回来,字段越多、关联越深,网络和内存压力越大。pipeline 把过滤和裁剪压到右集合内部执行。
- 示例:
{ $lookup: { from: "products", localField: "product_id", foreignField: "_id", as: "product", pipeline: [ { $match: { is_deleted: false } }, { $project: { name: 1, price: 1, _id: 0 } } ] } } -
pipeline中不能引用左集合字段(除非配合let+$expr,但会牺牲索引能力) - 避免在 pipeline 里用
$lookup嵌套,容易触发深度递归和内存溢出
关联字段类型必须严格一致
看似 trivial,却是线上高频踩坑点。比如左集合 localField 是 NumberLong,右集合 foreignField 存的是 NumberInt 或字符串,MongoDB 不会自动类型转换,索引失效,退化为全表扫描。
- 用
db.collection.findOne().yourField.constructor.name检查字段实际类型 - 迁移或导入数据时,尤其注意 JSON 导入可能把数字转成字符串
- 聚合中加
$convert强转类型是权宜之计,性能损失大,应从源头统一
真正卡住的往往不是单个优化点,而是多个条件叠加:没索引 + 顺序错 + 类型不一致 + 全字段拉取。先跑一遍 explain("executionStats"),看 totalDocsExamined 和 totalKeysExamined 是否接近,差太多说明索引没生效;再盯 executionTimeMillis 和 memoryUsageBytes,内存暴涨基本就是 pipeline 写法或数据量失控了。


















