$lookup 默认不丢数据,真正丢数据的是管道中误用的$match或未处理空数组。右表无匹配时输出空数组[],需用$arrayelemat和$ifnull安全取值;$unwind须设preservenullandemptyarrays:true保左表全量。

为什么 $lookup 默认会丢数据?
因为 $lookup 默认行为是左外连接(left outer join)的「左半边」——它确实保留左集合所有文档,但如果你在 pipeline 里写了 $match 或其他过滤阶段,就可能意外把左表文档也筛掉。真正丢数据的元凶往往不是 $lookup 本身,而是管道里没处理好空数组或未匹配时的 $$ROOT 上下文。
用 $lookup + pipeline 时必须加 $ifNull 和 $arrayElemAt 吗?
不一定,但多数场景需要。当右集合无匹配项时,$lookup 输出字段(比如 user_info)会是空数组 [],而非 null。直接解构会失败;想取单条关联数据就得显式处理:
-
$arrayElemAt: ["$user_info", 0]安全取首项(空数组返回null) -
$ifNull: [{ $arrayElemAt: ["$user_info", 0] }, {}]避免后续字段访问报错 - 若需保留全部匹配项(如一对多),就别用
$arrayElemAt,直接用$user_info字段即可
pipeline 中 $match 放错位置导致左表数据消失
这是最隐蔽的坑:在 $lookup 的 pipeline 内写 $match 是对右集合筛选,没问题;但如果在 $lookup 外面、同一级 stage 写 $match 去过滤左表字段(比如 { "status": "active" }),那当然会丢左表不满足条件的文档 —— 这和 $lookup 无关,是聚合顺序问题。
真正要保左表全量,关键点是:
-
$lookup必须放在$match(针对左表)之后,且不能被后续$match过滤掉左表字段 - 如果要用右表字段过滤结果,必须把
$match放进$lookup.pipeline里,或用$filter+$set在之后处理 - 示例错误写法:
{ $match: { "user_info.name": { $exists: true } } }—— 这会剔除user_info为空的左文档
用 $unwind 之前记得设 preserveNullAndEmptyArrays: true
很多人想把关联结果展开成多条,直接跟 $unwind: "$user_info",结果未匹配的文档全没了。默认 $unwind 会跳过 null 和空数组,等价于 { preserveNullAndEmptyArrays: false }。
正确做法:
{
$unwind: {
path: "$user_info",
preserveNullAndEmptyArrays: true
}
}
这样未匹配的文档会保留,user_info 字段值为 null,后续可用 $ifNull 统一兜底。
复杂点在于:空关联、一对多、字段嵌套三层以上时,$lookup 管道里的每个操作都要预判输出形态;不测试空数据集,上线后才发现漏了 20% 的订单记录,这种事真发生过。











