GridFS 无法参与 MongoDB 事务,因其底层需跨 fs.files 和 fs.chunks 两个集合写入,而事务不支持跨集合的流式操作;官方明确不支持多文档事务,驱动会静默忽略 session 参数,易产生孤儿元数据。
GridFS 不能参与 MongoDB 事务,任何试图在 session.startTransaction() 内调用 bucket.openUploadStream() 或 bucket.uploadFromStream() 的做法都会失败或产生孤儿文档 —— 这不是配置问题,是设计限制。
GridFS 底层写入必然跨两个集合:fs.files 和 fs.chunks。MongoDB 事务要求所有操作必须作用于同一事务上下文,而 GridFSBucket 的流式上传会先插入 fs.files 文档拿到 _id,再用该 _id 分批写入 fs.chunks —— 这个过程由驱动内部非事务性流控制,session 完全无法拦截。
官方文档明确标注:GridFS does not support multi-document transactions。即使你手动传入 session 参数,Node.js 驱动(v6.7+)也会静默忽略,不报错但也不生效。
bucket.openUploadStream() 后立刻发生 fs.files 插入,此时事务尚未 commit,但该写入已落盘fs.files 文档残留,形成“孤儿元数据”fs.* 集合无影响你需要原子性的从来不是“文件存进去”,而是“这个文件属于某条业务记录”。把上传和绑定拆开,才是符合实际约束的解法。
bucket.openUploadStream(),获取返回的 uploadStream.id(即 fs.files._id)collection.updateOne({ _id: orderId }, { $set: { attachmentId: fileId } })
fs.files 和 fs.chunks 仍存在,但业务侧无引用,可异步清理注意:不要在事务中做任何 bucket.* 调用,包括 bucket.find()、bucket.delete() —— 这些操作虽不报错,但同样游离于事务之外,无法保证一致性。
网络抖动、进程崩溃等会导致 fs.files 已写入但 fs.chunks 缺失或不完整。这类文件无法被 openDownloadStreamById() 正常读取,必须主动清理。
使用聚合管道定位孤儿文档:
db.fs.files.aggregate([ { $lookup: { from: "fs.chunks", localField: "_id", foreignField: "files_id", as: "chunks" } }, { $match: { "chunks.0": { $exists: false } } }])
确认结果无误后执行删除(只删 fs.files):
db.fs.files.deleteMany({ _id: { $in: [ /* 上述查出的 ObjectId 数组 */ ] } })
deleteMany fs.chunks,chunk 文档无独立语义,必须依赖 files_id 关联判断metadata 字段,便于后续按业务维度排查真正难的不是写代码,而是接受「GridFS 与事务天然是割裂的」这个事实。所有绕过它的尝试,最终都会在异常路径上付出更高维护成本 —— 孤儿文件、状态不一致、人工救火。把边界划清楚,比强行缝合更可靠。