$lookup 的 count 总是 0,主因是关联字段类型不一致(如 ObjectId vs 字符串)或未用 pipeline 配合 $match 过滤;$unwind 导致重复计数需用 $group 去重或改用 $size 统计;$lookup 不走索引、无缓存、跨分片受限且最终一致,实时性要求高时应避免直接依赖。

用 $lookup 做关联聚合时,为什么 count 总是 0?
因为默认的 $lookup 是左外连接,但没配 pipeline 或漏写了 $match 过滤条件,导致右表文档根本没被拉进来——尤其当关联字段类型不一致(比如一边是 ObjectId,另一边存的是字符串 ID)时,匹配直接失效。
- 务必检查关联字段类型是否严格一致:用
typeof或$type在 shell 里验证两边字段值 - 如果右集合需要条件过滤,必须用
pipeline形式写$lookup,不能只靠后续$match - 避免在
$lookup的localField上用表达式(如$toString),MongoDB 5.0+ 才支持,旧版本会静默失败
示例(安全写法):
{
$lookup: {
from: "orders",
localField: "userId",
foreignField: "_id",
as: "userOrders"
}
}
统计结果不准,$unwind 后重复计数怎么破?
$unwind 会把一个文档拆成多份,如果紧接着用 $count 或 $sum: 1,就会把“一个用户对应多个订单”算成多条记录,而不是“一个用户”。这不是 bug,是数据展开后的自然结果。
- 想按主文档维度统计(比如“有多少用户下过单”),先
$unwind再$group去重:用{ _id: "$_id" }聚合后再$count - 想统计关联项总数(比如“共产生多少订单”),就别
$unwind,改用$size或$sum: { $size: "$userOrders" } - 如果关联数组为空(
[]),$size返回 0,但$unwind后该文档会消失——这点常被忽略
实时性要求高,但 $lookup 慢得像查库快照?
$lookup 本身不走索引优化(除非配合 pipeline 中的 $match 提前过滤),而且每次聚合都重新拉取右表数据,没有缓存。它不是为高频、低延迟场景设计的。
- 确认右集合的
foreignField是否建了索引——没索引时,$lookup会全表扫描 - 如果只是要计数或存在性判断,别拉完整文档,用
pipeline只$project出必要字段,甚至只$count - 跨分片集群时,
$lookup不支持跨分片关联(4.2+ 有限支持,但要求分片键对齐),容易返回空结果而不报错
最终一致性下,怎么避免聚合结果“刚更新就过期”?
MongoDB 没有跨文档事务级一致性保证,$lookup 查到的右表数据,可能比左表文档早几毫秒——尤其在高并发写入时。这不是延迟,是模型决定的天然窗口。
- 业务上能接受几分钟延迟?那就加个
$match: { updatedAt: { $gt: { $subtract: [ "$$NOW", 300000 ] } } }过滤“太老”的关联数据 - 关键路径别依赖实时聚合,改用应用层双写 + 状态字段(比如订单表加
userStatus字段),用 TTL 索引自动清理陈旧状态 - 如果必须强一致,就得放弃
$lookup,改用应用代码分两步查,再用session.startTransaction()包裹读操作(仅限副本集,且性能代价大)
真正难的从来不是写对聚合语句,而是想清楚:这个数字到底要回答什么问题——是“此刻快照”,还是“业务可接受的近似”?后者往往更值得花时间定义清楚。

















