GridFS不是MongoDB独立功能,而是依赖客户端驱动实现的分块协议;它通过fs.files和fs.chunks两个集合存储元数据与数据块,需调用GridFSBucket等API才能正确分块、校验与读写,手动操作集合极易出错。

GridFS不是MongoDB的独立功能,而是驱动层协议
直接在mongosh里敲db.fs.files.insertOne(...)不能可靠存大文件——GridFS依赖客户端驱动对分块、元数据、校验逻辑的完整实现。MongoDB 6.0本身不提供“启用GridFS”的开关,它只提供fs.files和fs.chunks两个默认集合结构,真正起作用的是你用的驱动(如pymongo、mongodb-driver-sync)是否调用了GridFS API。
- 误以为只要把大文件塞进
BSON就能自动分块:不行,单个文档仍受16MB限制,手动写fs.chunks极易丢块或损坏md5校验 - 用
mongofiles工具时没指定--host或数据库名,默认连test库,结果文件存错地方 - Java项目里用
MongoClient但没显式构造GridFSBucket实例,而是直接操作collection,等于绕过了分块逻辑
Python用pymongo写入超大文件的最小可行步骤
以pymongo 4.6+(适配MongoDB 6.0)为例,核心是用GridFSBucket而非原始集合操作:
from pymongo import MongoClient
from gridfs import GridFSBucket
<p>client = MongoClient("mongodb://localhost:27017")
db = client["myapp"]
fs = GridFSBucket(db) # 注意:不是 GridFS(db)</p><h1>写入</h1><p>with open("/path/to/video.mp4", "rb") as f:
file_id = fs.upload_from_stream(
"video.mp4",
f,
metadata={"content_type": "video/mp4", "uploader": "admin"}
)</p><h1>读出</h1><p>grid_out = fs.open_download_stream(file_id)
with open("/tmp/downloaded.mp4", "wb") as f:
f.write(grid_out.read())
</p>
-
upload_from_stream内部自动按255KB分块(可传chunk_size_bytes=1024*1024调整),并写入fs.files和fs.chunks - 不要用
GridFS(db)(旧API),它已被标记为deprecated,且不支持6.0新增的collation等特性 - 若文件路径含中文或特殊字符,确保
open()用encoding="utf-8"仅适用于文本;二进制文件必须用"rb"
Java中避免因流未关闭导致文件截断
使用mongodb-driver-sync 4.11+时,GridFSUploadStream必须显式close(),否则最后几个块可能滞留在缓冲区不落盘:
GridFSBucket gridFSBucket = GridFSBuckets.create(database);
GridFSUploadStream uploadStream = gridFSBucket.openUploadStream("report.pdf");
<p>Files.copy(Paths.get("/tmp/report.pdf"), uploadStream);
uploadStream.close(); // 这行不能省!否则文件长度异常</p><p>// 读取时也需确保GridFSDownloadStream.close()
try (GridFSDownloadStream downloadStream = gridFSBucket.openDownloadStream(objectId)) {
Files.copy(downloadStream, Paths.get("/tmp/fetched.pdf"));
}
</p>
- 忘记
close()会导致fs.files.length比实际小,后续openDownloadStream读到空内容或FileNotFoundException - Spring Data MongoDB 4.x未封装GridFS写入API,仍需直接用原生驱动,别指望
@Document注解能自动触发GridFS - 上传大文件时加
timeout(30, TimeUnit.SECONDS)防止网络抖动卡死,但注意超时值要大于文件传输预期耗时
查询与清理容易被忽略的细节
GridFS文件删除不是deleteOne({ _id: ... })那么简单,必须用delete()方法才能同步清理fs.chunks:
// ✅ 正确:驱动自动删chunks + files
fs.delete(file_id)
<p>// ❌ 错误:只删了files,chunks变成垃圾数据
db.fs.files.deleteOne({ _id: file_id })
</p>
-
find()返回的是GridFSFile对象,其getLength()是总字节数,getFilename()是上传时指定名,不是_id - 按日期查旧文件:用
find({"uploadDate": {"$lt": ISODate("2025-01-01T00:00:00Z")}}),注意时间字段在fs.files里,不在fs.chunks - 生产环境定期跑
db.fs.chunks.validate()检查块完整性,尤其在异常断电后——损坏的块不会自动修复
真正麻烦的从来不是“怎么存”,而是“怎么保证每次都能完整读出来”。分块逻辑藏在驱动里,你既看不到也改不了;一旦上传中途失败,残留的fs.chunks记录得手动清理,否则磁盘悄悄涨满。











