直接看到“Payload document size is larger than maximum of 16MB”或“BsonMaximumSizeExceededException”即可确认是文档超16MB;必须用Object.bsonSize()精确测量单文档字节数,接近16777216即为临界点,db.collection.stats().avgObjSize无效。

怎么快速确认是文档超16MB导致写入失败
看到 Payload document size is larger than maximum of 16MB 或 BsonMaximumSizeExceededException,基本就是它——这不是配置能调的,BSON 协议层直接拦截,请求根本不会进存储引擎。别查日志里有没有慢查询,先做两件事:
- 在 shell 里用
Object.bsonSize(doc)精确测单文档字节数,接近16777216就是临界点 - 聚合管道中也要测中间结果:加
.explain("executionStats")看executionTimeMillisEstimate和内存峰值,超限常伴随 OOM 或 pipeline 中断 - 别信
db.collection.stats().avgObjSize——它只反映平均值,单个 outlier 才是真凶
GridFS 存完为啥不能直接聚合查内容
因为 fs.chunks 和 fs.files 是系统集合,结构固定、字段不可索引、不支持聚合操作:
- 对
fs.chunks执行$match或$group直接报错:errmsg: "cannot use $group in fs.chunks" -
fs.files.filename是字符串,默认没索引;uploadDate是 ISODate,但没业务语义字段 -
fs.chunks.data是BinData类型,聚合管道里连$regex都不认
真正能跑聚合的方案:GridFS + 元数据集合
核心是把“存”和“查”拆开:用 GridFS 存原始大 JSON,用独立集合存可索引、可聚合的业务字段,并通过 file_id 关联。
-
file_id必须是ObjectId,且值与fs.files._id完全一致,否则$lookup关联失败 - 只存摘要字段:比如
deviceId、timestamp、status、durationMs,别复制整个 JSON 结构进去 - 每个需用于
$match/$sort的字段都要单独建索引,例如:db.large_docs_meta.createIndex({ deviceId: 1, timestamp: -1 }) - 上传时必须同步写元数据,不能靠事后补——应用层要保证原子性(可用事务包裹
bucket.open_upload_stream()+元数据insertOne())
分表(子文档拆分)适合哪些场景
拆成多个子文档不是万能解,只适用于逻辑上可分割、且查询模式固定的场景:
- 子文档彼此独立(如用户评论列表),无跨文档统计需求
- 查询总是先取主文档,再按 ID 批量拉子文档(比如
db.comments.find({ postId: "abc" })) - 子文档数量可控(几百条以内),否则应用层合并成本高
- 更新单条子文档时,能接受统计字段(如
totalCommentCount)不同步——或者自己加双写逻辑
如果业务需要按时间范围对“十年日志”统一排序、分页跳转到第 10001 条,或者要实时计算 errorRate = errorCount / totalCount,那拆文档反而让问题更重:MongoDB 不支持跨文档数组。


















