GridFS中断后产生“孤儿块”是因为写入非原子:先插fs.files元数据(可能残缺),再分块写fs.chunks,中断导致部分chunks无对应有效files记录;清理必须先删fs.chunks再删fs.files,且严格按files_id关联。

GridFS中断后为什么fs.chunks里会有“孤儿块”
因为GridFS写入本身不是原子操作:先插入fs.files元数据(可选,取决于是否调用了openUploadStream()),再逐个写fs.chunks分块。中间任何环节——网络闪断、客户端崩溃、进程被杀——都可能只写入部分chunks,而files文档没落库,或已落库但length为 0、uploadDate为空、metadata.status卡在"uploading"。这些chunks找不到归属文件,就成了孤儿。
哪些情况会导致fs.files不完整却仍存在
fs.files是否提前写入,取决于你用的 API:
-
bucket.uploadFromStream()这类高层封装,通常会在上传开始前就插入fs.files文档(哪怕内容还不全) -
bucket.openUploadStream()则更可控:只有你显式调用.end()或.close()时,才最终写入完整元数据;中途断开,fs.files可能已存在但字段残缺
也就是说,即使你没手动插files,驱动也可能悄悄插了一条半成品记录——它有_id,但length === 0、uploadDate == null,且chunkSize和实际写入的chunks对不上。
为什么不能直接删fs.chunks里n值大的块来“清理”
有人想按n字段删掉“看起来是尾部”的块,这是危险操作:
-
n只是块索引,不保证连续;删除中间某块会导致整个文件无法拼合 - 不同文件可能共用同一个
files_id(比如重传时复用ID),盲目删n > 100会误伤其他文件 - 真正要清理的是“无主”的块,判断依据只能是
files_id是否在fs.files中存在且状态合法
正确做法永远是:先查出异常fs.files文档,再用它的_id精准过滤fs.chunks,执行deleteMany({ files_id: file._id })。
客户端重试时怎么避免重复写同一块
GridFS 不做块级去重,必须你自己加幂等控制:
- 上传前生成唯一
uploadId(如 UUID),写进metadata.uploadId - 每块上传前,检查
fs.chunks.find({ files_id: fileId, n: chunkN, "metadata.uploadId": "xxx" })是否存在 - 或者更轻量:用 Redis 存
"{fileId}_n_{n}"作为标记,SETNX成功才写块
关键点在于:这个校验必须发生在insertOne到fs.chunks之前,且不能依赖fs.files的状态——因为中断后files可能还没写,但chunks已经写了几个。
最常被忽略的一点:清理逻辑里没处理fs.files和fs.chunks的删除顺序。必须先删chunks,再删files;反过来会导致新上传同名文件时,旧chunks被意外复用,拼出损坏文件。











