$lookup性能瓶颈主因是foreignfield未建单字段索引,复合索引无效;localfield需前置$match过滤;分片下默认不推送到分片;事务中$lookup不保证快照一致性。

慢的主因不是数据量大,而是$lookup查的那张表(from集合)在foreignField上没建对索引——90% 的性能问题卡在这一步。
foreignField 必须单独建单字段索引,复合索引完全无效
很多人给products集合建了{_id: 1, category: 1},以为查_id能用上。错。$lookup只做等值匹配,且只用foreignField单个字段值,不走复合索引前缀。
-
db.products.createIndex({ _id: 1 })才有效;{_id: 1, name: 1}或{name: 1, _id: 1}都无效 - 如果
foreignField是嵌套字段,比如"metadata.code",索引必须写全路径:db.products.createIndex({ "metadata.code": 1 }) - 分片集群下,这个索引得在每个分片上都存在,且类型一致(不能一个分片是哈希索引,另一个是 B-tree)
localField 索引不是可选,而是前置加速条件
localField所在集合的索引,不直接加速$lookup本身,但决定了有多少文档会流进$lookup阶段。没它,$lookup可能被调用成千上万次。
- 错误写法:
[{$project: {productId: 1}}, {$lookup: {...}}]——$project后无法利用productId上的索引过滤,大量无关文档涌入 - 正确顺序:先
$match(带索引字段),再$lookup,最后$project - 如果
localField是数组(如tags: ["a","b"]),MongoDB 会对每个元素分别执行一次$lookup,索引压力翻倍,务必确认是否真需要这种语义
分片集群下 $lookup 默认不推送到分片执行
默认行为是把左表所有匹配文档拉到mongos再关联,网络和内存压力巨大。4.4+ 虽支持“交换算子下推”,但条件苛刻:
- 右表(
from集合)必须未分片,或与左表同 shard key -
foreignField必须是右表的_id或唯一索引字段 -
$lookup不能带pipeline参数——一旦写了pipeline: [{ $match: { status: "paid" } }],立刻退回到mongos执行 - 验证是否下推:开 profiler 后查 slowms 日志,找
"executionStages.stage": "LOOKUP_SHARDING",而不是"LOOKUP"
事务中 $lookup 读不到事务快照,一致性靠不住
事务内跑$lookup,查到的右表数据用的是最终一致视图,不是事务开启那一刻的 MVCC 快照。并发更新时,结果可能比事务内其他操作“旧几毫秒”。
- 这不是 bug,是 MongoDB 模型限制:
$lookup不参与事务快照机制 - 分片集群下更明显:各分片读取时间点不统一
- 强一致需求下,优先考虑冗余关键字段(如把
user.email嵌入orders),或事务内先findOne读出右表快照再处理 - 若非用
$lookup不可,至少加时间兜底:{$match: { updatedAt: {$gt: {$subtract: ["$$NOW", 5000] } } } },并确保updatedAt有索引
真正难处理的不是语法怎么写,而是接受一个事实:$lookup和事务本就不属同一抽象层。想靠它获得 ACID 级别的一致性,方向错了。











