GridFS不自动拆分大文件,需应用层控制切块逻辑、顺序校验与中断恢复;fs.files仅存元数据,二进制数据必须存入fs.chunks,且files_id、length、n字段须严格一致并准确计算。

GridFS 本身不提供“自动拆分大文档附件”的能力——它只是规范,不是上传协议。你必须在应用层决定何时拆、怎么拆、如何保证可恢复,否则写进去的文件大概率读不出来或内容错乱。
为什么不能直接把附件塞进 fs.files 当文档存
因为 fs.files 是元数据集合,只存描述信息(如 filename、length、chunkSize),真正二进制数据全在 fs.chunks。如果你跳过 chunks 直接往 fs.files 插一条记录,驱动读取时会报 FileNotFound 或静默返回空流。
-
fs.files._id必须和所有对应fs.chunks.files_id严格一致,类型也要匹配(比如 ObjectId vs string) -
fs.files.length字段必须等于原始文件字节总数,不能靠 guess 或fs.chunks.countDocuments()反推 - 如果附件本身是 JSON 或 BSON 结构化数据(比如带签名的 PDF 元数据包),别误以为能用
BinData塞进单个文档——超 16MB 还是得走GridFS
chunkSize 设为多大才合理
默认 255 KiB 是经验平衡值:太小 → fs.chunks 文档爆炸、索引膨胀、查询变慢;太大 → 单次网络失败重传成本高、内存缓冲压力大、无法做细粒度断点续传。
- 视频/音频流场景:保持默认 255 KiB,利于按时间戳随机 seek
- 归档类大压缩包(.tar.gz):可调到 1–2 MiB,减少 chunk 数量,但要求客户端有稳定连接和足够内存 buffer
- 移动端上传弱网环境:建议 ≤128 KiB,并启用
n字段校验(见下条) - 修改前务必确认驱动支持:Node.js 驱动从 v4.0+ 开始允许构造
GridFSBucket时传{ chunkSizeBytes: 1024 * 1024 },旧版只能靠 monkey patch
如何防止 n 字段错位导致文件拼接失败
fs.chunks.n 是唯一决定数据顺序的字段,MongoDB 不校验连续性,也不阻止重复 n。一旦错位,openDownloadStream 读出来的就是乱序二进制流。
- 不要用自增计数器生成
n:并发上传或重试时极易冲突 - 推荐用
Math.floor(offset / chunkSizeBytes)计算,其中offset是该 chunk 在原始文件中的字节起始位置 - 写每个 chunk 前,先查
fs.chunks.findOne({ files_id: uploadId, n: targetN }),存在则跳过(幂等) - 最终提交
fs.files前,执行fs.chunks.countDocuments({ files_id: uploadId }) === Math.ceil(fileSize / chunkSizeBytes),不等就中止
上传中断后怎么安全清理残留
没有跨集合事务,fs.files 和 fs.chunks 无法原子回滚。手动清理必须靠 uploadId 隔离 + 时间窗口判断。
- 所有 chunk 和 files 文档都带上
uploadId(UUID v4 最稳妥),且作为files_id和_id - 后台定时任务扫描
fs.files中uploadDate超过 24 小时、但fs.chunks对应数量不足的记录,标记为orphaned - 清理时先删
fs.chunks,再删fs.files,顺序反了会导致驱动认为“文件存在但无数据” - 避免用
deleteMany批量删 chunks:万一中途失败,可能漏删,建议用find().forEach()+deleteOne
真正难的不是切块,而是让每个 chunk 写对位置、每个 fs.files 插对时机、每次中断后知道从哪继续——这些逻辑没法靠驱动自动补全,得自己扛。











