直接查 files.md5 字段不可靠,因其默认不自动计算、常为空或为 Base64 编码;应本地计算文件 MD5(十六进制小写)并存入 metadata.file_md5,查询时用 {"metadata.file_md5": "..."} 精确匹配。

直接查 files.md5 字段会失败
很多开发者一上来就写 fs.find({"md5": "xxx"}),结果永远查不到——因为 GridFS 的 files.md5 字段默认不写入,或被旧驱动填成 Base64 编码的二进制哈希(比如 "MTIz"),而你本地算的是十六进制小写字符串(比如 "202cb962ac59075b964b07152d234b70")。类型和编码都不匹配,索引也用不上。
更糟的是:PyMongo、Node.js Driver 等主流驱动在调用 upload_from_stream() 或 openUploadStream() 时,不会自动计算并填充 md5,除非你显式传入 md5 参数(且仅部分版本支持),但那只是透传,不是校验。
- 别依赖
files.md5做秒传判断,尤其跨语言或升级驱动后 - 统一在上传前本地计算文件 MD5(十六进制小写),存进自定义字段,如
metadata.file_md5 - 查询必须走
{"metadata.file_md5": "..."},不能省略metadata.前缀
PyMongo 中如何安全地做存在性检查
核心是「只读一次文件」:边流式读取边算 MD5,算完立刻查库,命中则跳过上传;未命中再用同一文件句柄上传——避免二次读盘。
- 用
hashlib.md5()分块更新哈希,每次读8192字节,内存友好 - 查库必须用
find().limit(1).next(),不要用count_documents():前者能利用索引快速终止,后者要扫全表 - 确保已建索引:
db.fs.files.createIndex({ "metadata.file_md5": 1 }) - 示例关键逻辑:
file_md5 = calculate_file_md5("doc.pdf")
existing = fs.find({"metadata.file_md5": file_md5}).limit(1).next()
if existing:
return {"status": "hit", "_id": existing["_id"]}
else:
with open("doc.pdf", "rb") as f:
fs.upload_from_stream("doc.pdf", f, metadata={"file_md5": file_md5})
Node.js 驱动里 etag 和 md5 容易混淆
Node.js MongoDB Driver 的 openUploadStream() 支持传 options.md5,但它只是把值原样写进 files.md5 字段,**不是自动计算**。更危险的是:GridFS 默认生成的 etag 是所有 chunk 的 MD5 拼接后 Base64 编码的结果,它只反映 chunk 数据拼接顺序,不等价于整个文件原始字节的 MD5。
- 如果你用
etag当作文件唯一标识去查,大概率漏匹配或误匹配 - 上传时若手动传了
md5,务必确认它是你本地算出的十六进制小写字符串,不是etag解码出来的二进制 - 验证方式:用 Python 的
hashlib.md5()和 Node.js 的crypto.createHash("md5").update(buf).digest("hex")对同一文件算,结果必须完全一致
去重后怎么清理重复文件
查到已有记录只是第一步。如果业务要求「同名即覆盖」或「只留一份」,就得删掉旧文件——但不能手写 deleteMany({ "metadata.file_md5": ... }),否则会留下孤立 chunks。
- 必须用驱动提供的
delete()方法(PyMongo 是bucket.delete(file_id),Node.js 是bucket.delete(fileId)),它会原子性地删files文档 + 所有关联chunks - 并发场景下,先查后删之间可能有新上传插入,需加分布式锁(如 Redis SETNX)或改用「捕获 DuplicateKeyError → 删旧 → 重试」流程
- 如果已加了
{ filename: 1 }唯一索引,就不能靠「先删后存」了,得改成错误处理流程,否则put()会直接抛DuplicateKeyError: E11000 duplicate key error...
metadata.file_md5 匹配只是第一层筛,底层 chunks 缺失、错序、损坏,都可能导致下载失败或数据错乱。需要时得下沉到 fs.chunks 层手动验证 n 序列连续性。


















