MongoDB 单文档的 BSON 上限是 16MB,如果直接把一个 50MB 的 PDF 或者几百 MB 的视频塞进普通集合,写入会直接报错。GridFS 是 MongoDB 官方提供的大文件存储方案,它在驱动层把文件切割成多个小块,每个块默认 255KB,分散存储在 chunks 集合中,同时在 files 集合里保存文件级元数据。读取时驱动会按块序号重新拼接成完整文件,从应用侧看就像操作一个流一样。

理解 GridFS 的关键在于两个集合的协作关系。files 集合的一条文档对应一个逻辑文件,chunks 集合里则存放该文件的所有数据块。这种设计让 MongoDB 绕开了单文档大小限制,同时还能继续使用复制集、分片、索引和权限控制。下面从存储结构、代码实现和适用边界三个角度展开。
GridFS 的存储结构拆解
默认情况下,GridFS 使用 fs.files 和 fs.chunks 两个集合,前面的 fs 是 bucket 名称,可以在创建 GridFSBucket 时自定义。fs.files 中每条文档记录一个文件的元数据,常见字段包括 _id、length、chunkSize、uploadDate、filename、metadata。其中 length 表示文件总字节数,chunkSize 是分块大小,metadata 可以存放业务自定义信息,比如用户 ID、文件类型、来源系统等。这些字段在查询文件时会经常用到,建议在 filename 和 uploadDate 上建立索引,方便按文件名或上传时间检索。
fs.chunks 集合的文档结构更简单,主要包含 files_id、n 和 data 三个字段。files_id 指向 fs.files 中对应文件的 _id,n 是块的序号,从 0 开始递增,data 是二进制数据块。驱动在写入时会按 chunkSize 切分文件,最后一个块可以小于 chunkSize。读取时根据 files_id 查询所有块,并按照 n 升序排列后拼接。因此 fs.chunks 集合强烈建议在 files_id 和 n 上建立唯一复合索引,既能加速读取,又能防止同一个文件出现重复块序号。这个索引通常由驱动自动创建,但如果你手动管理集合,需要自己确认。
弄清楚这两个集合的关系后,就能理解为什么 GridFS 适合顺序读写而不适合随机修改。如果想改文件中间的几个字节,必须找到对应的块,取出二进制数据修改后再写回,过程比普通文件系统麻烦得多。这也是后面适用场景部分要重点讨论的内容。
上传与下载的代码实现
以 Node.js 官方 MongoDB 驱动为例,上传文件通常使用 GridFSBucket 的 openUploadStream 方法。它返回一个可写流,配合 fs.createReadStream 可以把本地文件流式写入 GridFS,避免一次性把整个文件载入内存。下面是一个完整的上传示例:
const { MongoClient, GridFSBucket } = require('mongodb');
const fs = require('fs');
async function uploadFile() {
const client = new MongoClient('mongodb://localhost:27017');
await client.connect();
const db = client.db('mydb');
const bucket = new GridFSBucket(db, {
bucketName: 'fs',
chunkSizeBytes: 1024 * 1024
});
const uploadStream = bucket.openUploadStream('report.pdf', {
metadata: { userId: 'u123', category: 'reports' }
});
fs.createReadStream('./report.pdf').pipe(uploadStream);
uploadStream.on('finish', function() {
console.log('上传完成,文件ID:', uploadStream.id);
client.close();
});
uploadStream.on('error', function(err) {
console.error('上传失败', err);
client.close();
});
}
uploadFile();
这段代码创建了一个名为 fs 的 bucket,并把 chunkSizeBytes 设置为 1MB。较大的块可以减少文档数量,对顺序读写性能有帮助,但也会增加单块内存占用。metadata 里可以挂载任意业务信息,后续查询时可以按这些字段过滤。上传完成后,uploadStream.id 就是 fs.files 集合中的 _id,下载时需要用到它。
下载文件同样通过流实现。使用 openDownloadStream 打开一个可读流,再 pipe 到本地文件写入流。下面的示例根据文件 ID 下载:
const { MongoClient, GridFSBucket, ObjectId } = require('mongodb');
const fs = require('fs');
async function downloadFile(id) {
const client = new MongoClient('mongodb://localhost:27017');
await client.connect();
const db = client.db('mydb');
const bucket = new GridFSBucket(db, { bucketName: 'fs' });
const downloadStream = bucket.openDownloadStream(new ObjectId(id));
const writeStream = fs.createWriteStream('./downloaded.pdf');
downloadStream.pipe(writeStream);
writeStream.on('finish', function() {
console.log('下载完成');
client.close();
});
downloadStream.on('error', function(err) {
console.error('下载失败', err);
client.close();
});
}
downloadFile('64b7f2a1e4b0a1b2c3d4e5f6');
除了 Node.js,Python 的 Motor 驱动、Java 的同步驱动也都有对应的 GridFSBucket API,用法类似。如果只是临时上传或导出文件,也可以使用 MongoDB 自带的 mongofiles 命令行工具,例如 mongofiles -d mydb put report.pdf 可以直接把文件写入 GridFS。不过在业务系统中,还是建议用驱动 API 以便控制元数据和错误处理。
GridFS 的适用场景与性能调优
GridFS 并不是所有大文件场景的最优解。它最合适的场景包括:单个文件超过 16MB 但通常在几十 MB 到几 GB 之间;文件需要和 MongoDB 中的业务数据保持一致的权限或事务上下文;文件数量较多但单文件并不算特别巨大;团队已经深度使用 MongoDB,不打算引入额外的对象存储服务。在这些情况下,GridFS 可以复用现有数据库的复制和备份机制,减少运维复杂度。
如果文件主要是几百 MB 甚至几 GB 的视频、镜像包,或者需要 CDN 分发、断点续传、随机区间读取,那么 GridFS 的性能和功能都会比较吃力。对象存储服务,如 S3 兼容存储,在这类场景下更成熟。另外,GridFS 不适合频繁修改文件内容,因为每次修改都可能涉及分块重写。把它当作一次性写入、多次读取的文件仓库会更合理。
性能调优方面,有几个关键点值得关注。第一,选择合适的 chunkSizeBytes,默认 255KB 对很多小文件够用,但如果主要处理的是几十 MB 以上的文件,可以调到 1MB 甚至更高,减少块数量和索引维护成本。第二,写入大量文件时尽量使用批量操作或并发控制,避免单连接串行写入形成瓶颈。第三,读取时务必让查询命中 files_id 和 n 的复合索引,否则 chunks 集合的扫描会非常慢。第四,删除文件时必须同时删除 files 和 chunks 中的相关文档,驱动提供的 delete 方法会处理这个流程,手动删除时容易留下孤儿块。第五,在分片集群中,chunks 集合的分片键选择要谨慎,通常选择 files_id 可以让同一文件的数据块尽量落在同一个分片,减少跨分片读取。
最后再强调一下一致性。GridFS 的写入过程会先插入 files 文档,再逐块插入 chunks,中间如果发生故障,可能出现 files 已存在但 chunks 不完整的情况。新版驱动在异常时会尽量清理已写入的内容,但生产环境仍建议定期检查孤儿文件和不完整块,或者在上传完成后校验文件长度是否与元数据一致。理解了这些边界,再决定是否启用 GridFS,会比单纯看到官方推荐就盲目使用稳妥得多。
总体来看,GridFS 用分块集合的方式解决了 MongoDB 单文档大小限制,适合中等体量文件的顺序读写和元数据管理。只要控制好 chunkSize、索引和删除流程,它可以在不少业务场景中稳定运行。对于真正的海量非结构化数据,还是建议提前评估对象存储方案,避免后期迁移成本过高。
MongoDB GridFS大文件存储文件分块修改时间:2026-10-04 05:49:55