不能直接删 fs.files 自动清理 fs.chunks,仅官方驱动调用 deleteMany()/deleteOne() 时才级联清理;mongosh 直接删仅删元数据,chunks 成孤儿块。

用 TTL 索引自动删 fs.files 元数据,但不会动 fs.chunks
GridFS 本身不支持原生 TTL,但你可以在 fs.files 集合上为自定义字段(比如 expiresAt)建 TTL 索引,MongoDB 后台线程会定期扫描并删除过期文档。这一步只清元数据,fs.chunks 里的块全都会变成孤儿块,磁盘空间照占不误。
必须手动补上清理 fs.chunks 的逻辑,否则几个月后你会发现磁盘快满了,而 db.fs.files.countDocuments() 却显示文件数很少。
-
db.fs.files.createIndex({"expiresAt": 1 }, { expireAfterSeconds: 0 })—— 注意expireAfterSeconds: 0表示“一过期就删”,别写成正数 - 写入时必须显式传
expiresAt字段,类型是Date,不能是字符串或毫秒数;uploadDate是上传时间,业务上通常不等于过期时间 - TTL 扫描间隔约 60 秒,对秒级时效要求的场景不能依赖它做唯一保障
定时任务两阶段清理:先查被 TTL 删除的 files_id,再删对应 chunks
不能靠“查 fs.files 还剩哪些”反推要删哪些 fs.chunks——因为 TTL 删除是异步的,你查的时候可能还没执行完。最准的方式是监听变更流(change stream),但私有化部署常禁用它。退而求其次,用“反查法”:
先找出所有在 fs.files 中已不存在、但在 fs.chunks 中仍有引用的 files_id,再批量删这些 chunk 文档。
- 确保
fs.chunks上有files_id索引:db.fs.chunks.createIndex({"files_id": 1 }),否则聚合或$in查询极慢 - 执行反查语句(注意加
.limit(1000)分批,避免内存爆):db.fs.chunks.aggregate([{$group: {_id: "$files_id"}}, {$lookup: {from:"fs.files", localField:"_id", foreignField:"_id", as:"file"}}, {$match: {"file.0": {$exists: false}}}, {$project: {_id: 1}}]) - 拿到
_id列表后,分批执行:db.fs.chunks.deleteMany({ files_id: { $in: [/* ObjectId 数组 */] } })
Node.js 定时脚本里别直接操作 collection,要用 bucket.delete()
很多团队写清理脚本时图省事,直接用 db.collection("fs.files").deleteMany(...),这是错的。官方驱动只有通过 GridFSBucket.delete() 才能保证先删 fs.chunks、再删 fs.files 的顺序和原子性(MongoDB 4.0+ 副本集支持事务回滚)。
-
bucket.delete()只接受ObjectId,不是文件名或字符串 ID;得先findOne()拿到真实_id,再传进去 - 如果业务用了软删除(比如
metadata.status === "deleted"),就别调delete(),改用updateOne()更新状态字段 - 误删不可逆,脚本里加日志:删前打印匹配数,删后验证
db.fs.chunks.countDocuments({ files_id: fileId }) === 0
删完磁盘不释放?别急着重装,先 compact 再 validate
即使 fs.files 和 fs.chunks 文档都清空了,WiredTiger 引擎也不会立刻归还磁盘空间。这不是 bug,是正常行为。但线上客户看到“删了 20G 文件,df -h 却没变化”,第一反应就是“删失败”。
- 先运行
db.runCommand({ compact: "fs.chunks" })和db.runCommand({ compact: "fs.files" })—— 注意:仅 WiredTiger 支持,且需管理员权限 - 副本集必须在主节点执行;分片集群得对每个 shard 单独 compact
- 更稳妥的做法是定期跑
db.fs.chunks.validate(),它能发现孤儿块并报错,比等磁盘告警再处理强得多
真正麻烦的从来不是“怎么删”,而是“删谁”和“删完怎么验”。expiresAt 字段漏设、files_id 缺索引、compact 忘在主节点执行——这三个点,线上出问题十次有八次栽在这儿。











