GridFS是MongoDB用来存储超过单文档大小限制文件的规范,它将大文件切分为多个chunk块存入chunks集合,并把文件元信息记录在files集合中。当系统读取某一个文件时,MongoDB驱动会先到files里找到对应记录,再依据files_id与n序号去chunks里按顺序取出所有块拼接。故障码1860表示在拼接过程中发现某个序号的块不存在,也就是逻辑上文件完整、物理上块缺失,这会直接中断读取操作。

理解故障码1860的触发机制
要修复1860错误,必须先理解它为何产生。GridFS在写入文件时,驱动会把文件按chunkSize拆分,比如默认255KB一块,依次写入chunks集合,每块带有files_id和n字段。写完后向files插入一条元信息,标记文件长度与块数。正常读取时,驱动用files_id查chunks,预期拿到从0到N-1连续序号的所有块。如果其中某块被后台清理脚本误删、副本集节点同步落后被回滚,或者底层磁盘坏道导致文档不可读,查询就会少一块,触发1860。
这种错误和普通“文件不存在”不同。如果是files里没记录,驱动会报文件未找到;而1860说明元信息还在,只是块不完整,往往更隐蔽。比如在清理老旧附件时,运维可能只删了chunks却忘了关联files,或用了错误条件批量删块。此时业务端表现可能是部分文件能下、部分报1860,极具迷惑性。因此排查时要以files_id为线索,而不是只看文件表。
从数据库内部看,1860并非MongoDB内核主动扫描发现,而是读取路径上“期望的块没查到”的被动报错。也就是说,只要不去读那个残缺文件,错误就不会暴露。这也导致很多团队在块丢失后很久才察觉,直到用户访问到特定资源才引爆。所以仅靠应用报错来发现是不够的,需要主动做块一致性校验。
定位缺失块的具体操作步骤
定位的第一步是拿到报错文件的_id。应用日志里通常会带上files_id,如果没有,可以通过驱动异常对象提取。拿到标识后,在mongo shell中执行反查:先看files里该文件的length与chunkSize,算出应有块数;再用聚合看chunks里实际存在的n分布。如下示例用JavaScript计算缺失序号。
// 假设文件_id为 ObjectId('64a1b2c3d4e5f60708091011')
var fileId = ObjectId('64a1b2c3d4e5f60708091011');
var meta = db.fs_files.findOne({_id: fileId});
var expectChunks = Math.ceil(meta.length / meta.chunkSize);
var exist = db.fs_chunks.distinct('n', {files_id: fileId}).sort();
var missing = [];
for (var i = 0; i < expectChunks; i++) {
if (exist.indexOf(i) === -1) {
missing.push(i);
}
}
print('期望块数: ' + expectChunks);
print('缺失序号: ' + JSON.stringify(missing));
上述代码先读取元信息,再用distinct取出已有块序号,循环比对得出缺口。如果missing数组非空,就确认了1860的根因。接下来要判断丢失范围:是单个文件偶尔丢一块,还是一批文件连续丢。可以写脚本遍历近期上传的files,批量输出缺失情况,定位是否是某次运维操作引起。
除了自写脚本,也可利用现有驱动的诊断接口。例如Node.js的gridfs-stream在读取时会抛错并附带expected与found信息;Python的pymongo配合gridfs模块,可在get失败时捕获CorruptGridFile异常,里面包含缺失块号。把这些异常集中到日志平台,就能快速建立缺失块清单,而不必每次手动查库。
修复策略与后续防护
修复方式取决于是否有备份。若开启了定期逻辑备份或文件系统快照,最直接的是从备份库导出对应files_id的chunks文档,恢复到生产集合。注意恢复时要连同files元信息一起核对,避免块回来了但长度字段不对。恢复后再次跑校验脚本确认missing为空,再通知业务重试。
// 从备份恢复缺失块示例(假设备份库为 backupDb)
var missing = [3, 7];
var fileId = ObjectId('64a1b2c3d4e5f60708091011');
missing.forEach(function(n) {
var chunk = backupDb.fs_chunks.findOne({files_id: fileId, n: n});
if (chunk) {
db.fs_chunks.insert(chunk);
print('已恢复块: ' + n);
} else {
print('备份也无此块: ' + n);
}
});
如果没有备份且业务允许,可选择标记文件失效。具体做法是在files文档加一个status: 'broken'字段,应用读取时跳过或直接重传。对于用户上传类场景,可引导用户重新上传,同时在后台异步删除残缺的files与孤儿chunks,释放空间。切忌留着半截文件不处理,否则1860会反复出现。
长期防护比单次修复更重要。建议部署每日巡检任务,用类似前面的脚本扫描fs.files最近N天记录,发现缺失立刻告警。还可以在写入链路加双写校验:块落盘后马上用count确认数量匹配再返回成功。副本集环境要确保writeConcern设为多数派,避免主节点写完后未同步就被切换,造成块丢失假象。监控上把1860纳入核心指标,一旦冒头自动暂停相关清理任务,将人为误操作概率压到最低。