gridfs默认使用fs.files和fs.chunks两个集合,前者存储文件元数据(如文件名、大小、上传时间等),后者存储分块的二进制数据(每块默认255kb,含files_id、n序号和data字段)。

MongoDB 本身不支持存储超过 16 MiB 的单个 BSON 文档,所以直接 insertOne 一个超大文件会报错:Document too large。必须用 GridFS —— 它不是“插件”或“扩展”,而是 MongoDB 官方定义的一套分块存储规范,由驱动和底层集合结构共同实现。
GridFS 实际用了哪两个集合?
GridFS 在数据库里默认创建两个集合:fs.files 和 fs.chunks(前缀 fs 可自定义)。前者存元数据:文件名、大小、上传时间、contentType、自定义 metadata 字段等;后者存真实数据块,每个文档含 files_id(对应 fs.files._id)、n(块序号,从 0 开始)、data(BinData 类型的二进制块)。
注意:fs.chunks 文档没有 _id 索引,但有复合索引 { files_id: 1, n: 1 },读取时靠这个保证顺序拼接。别手动删 fs.chunks 里的某几块,否则文件就损坏了。
chunkSizeBytes 参数怎么影响读写行为?
初始化 GridFS bucket 时可传 chunkSizeBytes,默认是 255 * 1024(即 255 KiB)。这个值不是越小越好,也不是越大越好:
- 设太小(如 8 KiB):块数暴增,
fs.chunks文档数量多,插入/查询时网络往返和磁盘 I/O 次数变多,元数据开销占比上升 - 设太大(如 2 MiB):单次读一块就可能把内存打满,尤其做范围读(比如只取视频第 10–15 秒)时,驱动仍得加载整块再裁剪,浪费带宽和 CPU
- 修改后只对新上传文件生效,旧文件 chunk 大小不变,也不能跨文件混用不同 chunkSize
实践中,255 KiB 是平衡点;若大量存 50–200 MiB 视频,可试 512 * 1024;若存上万个小于 1 MiB 的日志包,保持默认即可。
用 mongofiles 还是驱动 API?关键区别在哪
mongofiles 是命令行工具,适合运维批量导入导出,或快速验证 GridFS 是否正常工作。但它不支持流式上传、断点续传、自定义 metadata 写入(只能靠 -t 设 content-type),也拿不到上传后的 _id 做后续关联。
生产代码一律用驱动 API,例如 Node.js 的 GridFSBucket:
const bucket = new GridFSBucket(db, { bucketName: 'uploads' });
const uploadStream = bucket.openUploadStream('report.pdf', {
metadata: { userId: 'u_abc123', tags: ['q4', 'final'] }
});
fs.createReadStream('./report.pdf').pipe(uploadStream);
重点:openUploadStream 返回的是可写流,能配合 pipe 或 write() 分段写;openDownloadStream 返回可读流,支持 skip/limit 做偏移读取 —— 这才是“跳转播放”的底层能力。
为什么不能用 GridFS 做原子更新?
因为 GridFS 本质是多文档操作:改文件内容 = 删旧 fs.files + 删所有对应 fs.chunks + 插新 fs.files + 插一堆新 fs.chunks。MongoDB 不支持跨集合事务回滚(即使开启事务,fs.files 和 fs.chunks 也无法在一个事务里原子提交)。
真要“更新文件”,正确做法是:
- 上传新版本,拿到新
_id - 用
updateOne原子更新业务集合里指向该文件的字段(比如{ fileId: ObjectId('...') }) - 异步清理旧
fs.files和fs.chunks(调delete方法,它会自动删两处)
漏掉最后一步,旧块会一直占磁盘空间 —— 这是线上最容易被忽略的存储泄漏点。











