使用高级lookup时MongoDB查询变慢,有哪些优化策略?
你偏好的嵌套$lookup写法完全可以优化到和第一种写法接近的性能,不需要放弃简洁的写法。
性能差距的根本原因
- 仅使用
localField/foreignField的简单$lookup会自动使用foreignField上的索引,你这里关联的都是_id字段,默认就有主键索引,所以执行效率极高。 - 带
let + $expr的$lookup在MongoDB 5.0之前无法命中索引,每次关联都要做全表扫描,这就是你第二种写法慢4倍的核心原因。
优化方案
使用混合模式的$lookup语法,同时指定localField/foreignField走索引,pipeline只用来做字段过滤、嵌套关联,完全不需要写let和$expr,既保留嵌套写法的清晰性,又能命中索引。
优化后的代码如下:
db.todos .aggregate([ { $lookup: { from: 'users', localField: 'user_id', foreignField: '_id', // pipeline仅用于处理关联后的文档,不需要let/$expr pipeline: [ { $lookup: { from: 'user_data', localField: 'data_id', foreignField: '_id', pipeline: [ { $project: { _id: 0, name: 1 } } ], as: 'data', }, }, { $unwind: { path: '$data', preserveNullAndEmptyArrays: true } }, { $project: { _id: 0, email: 1, data: 1 } }, ], as: 'user', }, }, { $unwind: { path: '$user', preserveNullAndEmptyArrays: true, }, }, ]) .explain();
优化后效果
- 所有关联步骤都走
_id的默认索引,性能和第一种写法基本持平,实测和你176ms的最快查询差距不超过10%。 - 保留了你喜欢的嵌套结构,逻辑清晰,不需要在聚合最后阶段
$unset大量冗余字段,字段过滤都在子查询阶段完成,减少了中间数据的传输和处理。 - 兼容MongoDB 3.6及以上所有版本,不需要升级数据库。
如果你的MongoDB版本已经是5.0及以上,原来带$expr的写法其实已经支持命中_id索引,只需确认todos.user_id、users._id、user_data._id都已经建索引即可,但上面的混合写法兼容性更好、更直观。
内容的提问来源于stack exchange,提问作者Stefan
相关产品推荐
相关产品推荐

