GridFS适合存储超过16MB限制的大文件,如GB级音视频、PDF等;小文件(如10MB)也必须用GridFS,否则写入失败。它通过分块(默认255KB/chunk)和元数据分离实现,规避单文档上限,同时保障原子性、并发安全与范围读取能力。

GridFS 适合存什么大小的文件
MongoDB 文档有 16MB 上限,BinData 字段直接存二进制会很快撞到这个限制。超过几 MB 的图片、PDF、音视频,用普通字段存不是报错就是被截断——错误信息通常是 Document too large 或写入后读出来长度不对。
GridFS 不是“另一个存储引擎”,而是客户端侧的分块协议:自动把大文件切为默认 255KB 的 chunks 文档,元数据存在 files 集合。只要单个 chunk 不超 16MB(它绝不会),整体文件可以是 GB 级。
- 小文件(BinData 更快,少一次集合查询
- 中等文件(100KB–10MB):两者都行,但 GridFS 提供流式读写和部分读取能力
- 大文件(>10MB):必须用 GridFS,否则写不进去
为什么不能只靠 application 层分片再存多个文档
有人试过手动把文件切成 buffer 数组,每个存一个文档,加个 seq 字段排序。这看似绕开了 GridFS,实际埋了三个坑:
- 原子性丢失:上传中途失败,数据库里残留不完整碎片,application 得自己清理
- 并发读取风险:多个 client 同时读同一个“伪文件”,可能拿到不同版本的 chunk 组合
- 缺失范围读支持:想只下载 PDF 的第 3 页?得把所有 chunk 拉下来再拼,没法像 GridFS 那样用
skip/limit或start/end参数直接定位
GridFS 的 fs.openDownloadStream() 和 fs.openUploadStream() 内置了这些逻辑,不是语法糖,是协议级保障。
Node.js 里用 mongodb 驱动操作 GridFS 的关键点
新版驱动(v4+)把 GridFS API 收进了 GridFSBucket 类,不是挂载在 db 对象上。容易漏掉初始化步骤,导致调用 openUploadStream 报 TypeError: fs.openUploadStream is not a function。
正确姿势:
const { MongoClient, GridFSBucket } = 'mongodb';
const client = new MongoClient(uri);
await client.connect();
const db = client.db('mydb');
const fs = new GridFSBucket(db, {
bucketName: 'uploads' // 默认是 'fs',改名要前后一致
});
-
bucketName必须显式传,否则默认用fs,但你建的集合如果是uploads.files,就查不到 - 上传时
filename是必填元数据,不然find()查不到;contentType要手动设,不会自动推断 - 删除文件用
delete()传_id,不是remove()—— 后者只删files集合,chunk 还在,磁盘白占
GridFS 的性能和运维隐性成本
它解决了“存不下”的问题,但没解决“读得慢”或“备份麻烦”。每个文件变多条文档,索引体积涨、oplog 压力大、mongodump 备份时间明显变长。
- 高频小文件(比如用户头像缩略图):考虑合并存到对象存储(S3/MinIO),Mongo 只存 URL
- 需要全文检索或按内容查文件:GridFS 不支持,得额外抽文本建索引,不如直接存 Elasticsearch
- 跨集群迁移时:
chunks和files集合必须一起导,漏一个就文件损坏
真正卡住人的往往不是“怎么用”,而是“什么时候不该用”——尤其当团队开始往里面塞日志、监控快照、临时导出文件的时候。











