MongoDB聚合输出受16 MiB BSON文档大小硬限制,$facet、$group等阶段易触发超限;allowDiskUse仅缓解中间计算压力,不解除输出限制;有效方案为$ out写入新集合、分页查询或按时间分片聚合。

因为聚合最终输出是一个 BSON 文档,而 MongoDB 对单文档有 16 MiB 硬上限——不管它来自 $group、$facet 还是 $out,只要序列化后超过 16 MiB,就立刻报错,没有例外。
聚合结果本质就是一个 BSON 文档
很多人误以为“聚合是流式处理”,其实不然:db.collection.aggregate() 默认返回一个游标,但游标里每一批(batch)数据仍是独立的 BSON 文档。当你用 .toArray()、forEach 或驱动自动展开时,客户端会尝试把整个结果集拼成一个 JS 对象;如果后端没做分页或截断,服务端也可能在内存中构造出超大中间文档(尤其在没用 allowDiskUse: true 且管道复杂时)。
- 错误典型表现:
errmsg: "BSONObj size: 16777220 (0x1000004) is invalid. Size must be between 0 and 16777216(16MB)" -
$facet更危险:它强制把多个子管道结果塞进同一个外层文档,即使每个子管道输出不大,加起来也容易突破 16 MiB -
$group后没跟$project剔除冗余字段,容易把原始大字段(如 base64 图片、日志文本)全带出来
allowDiskUse 不能解决 16 MiB 输出限制
allowDiskUse: true 只影响聚合过程中的临时数据(比如 $sort 或 $group 的中间状态)能否写磁盘,它完全不改变最终输出文档的 BSON 大小约束。
- 你可以在
$group阶段汇总 100 万条记录,只要中间计算不爆内存,allowDiskUse就能帮上忙 - 但如果
$group结果本身是个含 50 万个元素的数组,每个元素带 30KB 描述字段,那最终文档轻松超限 - 此时加
allowDiskUse不会抑制错误,反而可能让失败来得更慢(因为磁盘 IO 掩盖了瓶颈位置)
真正有效的绕过方式只有三种
不是“绕过限制”,而是承认 16 MiB 是协议层铁律,从设计上规避:
- 用
$out把结果写入新集合:输出不再走 BSON 响应体,而是存为独立文档,无 16 MiB 限制 —— 但要注意,这会丢失游标语义,后续查需要再find - 加
$limit+$skip分页:适用于前端列表场景,避免一次性拉全量;注意$skip在大数据集性能差,优先用范围查询(如_id > lastId) - 拆成多次聚合:比如按时间范围切分,每次只聚合一天数据,应用层合并;适合离线统计,不适合实时接口
最容易被忽略的一点是:哪怕你开了分片、调高了 wiredTigerCacheSizeGB、甚至用 mongos 路由,只要最终响应体 BSON 序列化后 >16 MiB,连接就会被直接断开 —— 这个边界在客户端解析阶段就卡死了,服务端连日志都未必来得及打。


















