查 fs.chunks 是否存在对应 files_id 的文档:先通过 db.fs.files.findone({ filename: "xxx.pdf" }) 获取文件 _id,再执行 db.fs.chunks.countdocuments({ files_id: objectid("...") }),对比理论 chunk 数 math.ceil(length / chunksize),若 count 少则缺失,多则可能存在孤儿块;同时检查 n 字段是否连续,可用聚合查找断点;还需排查上传中断、驱动未 flush、副本集同步延迟或分片不可达等问题。

查 fs.chunks 是否存在对应 files_id 的文档
GridFS 文件下载失败、返回空或截断,八成是 fs.chunks 里缺了部分块。核心判断逻辑很简单:先从 fs.files 拿到目标文件的 _id(即 files_id),再查 fs.chunks 里有多少个匹配该 files_id 的 chunk。
实操建议:
- 用
db.fs.files.findOne({ filename: "xxx.pdf" })拿到文档,记下_id字段值(注意是ObjectId类型,不是字符串) - 执行
db.fs.chunks.countDocuments({ files_id: ObjectId("...") }),对比结果与files文档里的chunkSize和length:理论 chunk 数 =Math.ceil(length / chunkSize) - 如果 count 少于理论值,说明缺失;如果 count 多出几个,可能是上传中断残留的孤儿块
检查 n 字段是否连续且无跳号
fs.chunks 中每个 chunk 有 n 字段表示序号(从 0 开始),它必须严格连续。一旦中间缺了 n: 5,后续所有 chunk 都会错位——读取时会提前结束或拼接错乱。
实操建议:
- 运行
db.fs.chunks.find({ files_id: ObjectId("...") }).sort({ n: 1 }).toArray(),人工扫一遍n值序列 - 更省事的方法:用聚合查断点:
db.fs.chunks.aggregate([ { $match: { files_id: ObjectId("...") } }, { $sort: { n: 1 } }, { $group: { _id: null, ns: { $push: "$n" } } }, { $project: { gaps: { $setDifference: [ { $range: [ 0, { $size: "$ns" } ] }, "$ns" ] } } } ])—— 输出gaps数组就是缺失的n值 - 注意:
n跳号 ≠ 一定损坏,但凡上传中途崩溃(如网络断、进程 kill),就极易留下空号
确认上传是否被中断或驱动未 flush
缺失 chunk 最常见于上传流程异常终止。GridFSBucket 的 uploadStream 必须显式 close() 或 abort(),否则最后几个 chunk 可能只写了一半或根本没落盘。
实操建议:
- 检查应用日志里是否有
uploadStream.close()被跳过、超时未执行,或io.Copy返回错误后没做 cleanup - Go/Java/Node.js 驱动中,若用
uploadStream.abort(),它不会回滚已写 chunk,只会标记失败——这些 chunk 就成了“半成品”,files文档可能都不存在,但chunks里已有若干files_id为null或非法值的脏数据 - 临时验证:在
fs.chunks中查{ files_id: null }或{ files_id: { $type: "string" } },这类文档大概率是中断上传残留
别忽略 replica set 主从同步延迟导致的“假缺失”
在副本集部署中,如果应用连的是 secondary 节点读取,而上传刚在 primary 完成,secondary 还没同步完 fs.chunks,就会表现为“查不到 chunk”。这不是数据丢失,而是读写分离下的短暂不一致。
实操建议:
- 确认读请求是否用了
readPreference=primary(尤其下载场景必须强一致性) - 用
db.adminCommand({ replSetGetStatus: 1 })看 secondary 的optimeDate是否明显落后于 primary - 不要在 secondary 上跑
countDocuments判断缺失——它可能只是还没追上
files_id 存在但某些 chunk 永远查不到,得靠分片状态诊断和人工修复。











