聚合结果单文档超16mb会直接报错, errmsg: "bsonobj size too large",必须改用$out或$merge落地到集合;$out全量替换,$merge支持增量更新;慎用$push、$addtoset等易生成超大文档的操作,且allowdiskuse仅缓解内存压力,不解决bson大小限制。

聚合结果单文档超16MB会直接报错,errmsg: "BSONObj size too large",这不是配置能绕过的硬限制,必须改用$out或$merge把中间结果落地到集合。
聚合管道输出超限的典型错误现象
执行 db.collection.aggregate([...]) 时遇到以下任一报错,基本可判定是结果文档体积超标:
-
errmsg: "BSONObj size too large"—— 最常见,说明某阶段输出的单个文档(如$group后的汇总行)本身已超 16MB -
errmsg: "exceeded memory limit for $group, but didn't allow external sort"—— 实际是内存+文档大小双重超限,allowDiskUse: true只能缓解内存压力,不解决 BSON 大小限制 - 使用
$facet或嵌套$group后返回游标为空或中断 —— 内部构造的中间文档在序列化前就被拒绝
用 $out 或 $merge 替代直接返回游标
这是最直接、最稳定的解法:让聚合结果不经过驱动层组装成 BSON 文档,而是写入目标集合。MongoDB 对写入集合的文档没有单条 16MB 限制(只要每条写入的文档自身不超限)。
-
$out会**全量替换**目标集合,适合一次性导出、ETL 场景;示例:db.logs.aggregate([{$group: {_id: "$deviceId", count: {$sum: 1}}}, {$out: "device_summary"}]) -
$merge支持**增量更新/插入/保留**,需指定on字段和whenMatched/whenNotMatched策略,适合持续同步;示例:{$merge: {into: "daily_stats", on: "_id", whenMatched: "replace", whenNotMatched: "insert"}} - 注意:
$out和$merge都要求聚合最后阶段必须是它们之一,不能跟在$limit或$project后面再加
避免在聚合中生成超大中间文档
有些写法看似合理,实则极易触发单文档超限,尤其在处理宽表或嵌套数组时:
- 慎用
$push+ 大数组:比如{$group: {_id: "$user", allEvents: {$push: "$$ROOT"}}},若单用户事件超千条,JSON 序列化后轻松破 16MB - 避免
$addToSet存完整子文档:它去重逻辑需深比较,且结果仍是数组,体积不可控 - 用
$count、$sum、$avg等标量聚合代替全量收集;真要抽样可用$sample,别用$slice拼大数组 - 如果必须保留部分原始字段,优先用
$project显式列出关键字段,而不是{$project: {data: 0}}这类排除写法——后者仍会加载完整文档再裁剪
Java 驱动里 allowDiskUse(true) 的真实作用
这个选项只影响聚合阶段的**内存使用策略**,和 BSON 文档大小限制无关:
- 它允许
$group、$sort等阶段把临时数据溢出到磁盘,避免"exceeded memory limit"报错 - 但它**不会**让最终输出的单个文档变小;如果
$group输出一条含 5000 个对象的数组,该文档依然会因 >16MB 被拒绝 - Java 中正确写法是:
AggregateIterable<document> result = collection.aggregate(pipeline).allowDiskUse(true)</document>,但仅当 pipeline 末尾是$out或$merge时才真正安全
最容易被忽略的一点:即使你用了 $out,如果 pipeline 中间某步(比如 $lookup 做了无限制左连接)生成了超大内联文档,聚合仍会在写入前失败。必须确保每个阶段输出的单个文档都可控——这比调参数更重要。











