确认碎片需查两组指标:一是db.fs.chunks.stats().wiredTiger"block-manager"比db.fs.chunks.stats().size大20%以上,表明严重内部碎片;二是explain("executionStats")中totalDocsExamined远超nReturned,说明B-tree空洞化。

GridFS 本身不产生磁盘碎片,真正拖慢查询、虚占磁盘的,是 fs.files 和 fs.chunks 上老化或结构失衡的索引,以及 WiredTiger 底层未回收的空闲页。
怎么确认碎片真存在,而不是误判?
别只看文档数或 db.fs.chunks.count()——这毫无意义。重点查两组指标:
- 查底层空间浪费:
db.fs.chunks.stats().wiredTiger["block-manager"]["file size in bytes"]和db.fs.chunks.stats().size对比;前者比后者大 20% 以上,基本可判定存在严重内部碎片 - 查索引效率:
db.fs.chunks.find({ files_id: ObjectId("...") }).explain("executionStats"),如果totalDocsExamined远大于nReturned(比如 10 倍以上),说明扫描跳过了大量无效页,B-tree 已空洞化
reIndex 和 compact 必须分开用,且顺序不能错
reIndex 只优化 B-tree 结构,不释放物理空间;compact 重写数据文件并回收空闲页,但要求停写。两者目的不同,常需配合:
- 先执行
db.fs.chunks.reIndex()和db.fs.files.reIndex()—— 这是集合级排他锁,主节点上会阻塞所有写入(包括新上传、删除),务必在业务低峰或从节点上操作 - 再执行
db.runCommand({ compact: "fs.chunks" })—— 仅支持 WiredTiger 引擎,且必须在副本集 Primary 或分片集群的 shard 节点上运行;执行期间该集合完全不可读写 -
db.runCommand({ compact: "fs.files" })可选但建议执行,虽文档少,但能清理残留索引碎片,避免后续操作隐性变慢
为什么删完文件磁盘空间还不降?孤儿元数据必须清
99% 的 fs.files 持续膨胀,是因为只删了 fs.chunks 或业务表引用,却漏掉调用 GridFSBucket.delete() 清理元数据,导致大量无对应 chunk 的文档堆积。
安全清理步骤:
- 先建加速索引:
db.fs.chunks.createIndex({ files_id: 1 }) - 用聚合查孤儿:
db.fs.files.aggregate([ { $lookup: { from: "fs.chunks", localField: "_id", foreignField: "files_id", as: "chunks" } }, { $match: { "chunks.0": { $exists: false } } }, { $project: { _id: 1 } } ]) - 拿到
_id数组后,执行:db.fs.files.deleteMany({ _id: { $in: [...] } })
这步不做,碎片几周内就会复发——reIndex 和 compact 都只是临时止血,不是根治。
compact 失效时,只能导出→清空→重建
当 wiredTiger.block-manager.file bytes available for reuse 仍远高于预期,说明碎片太散,compact 效率极低。此时必须走硬核路径:
- 用
mongodump --gzip --db your_db --collection fs.chunks --collection fs.files -o /tmp/gridfs-backup导出剩余有效数据 - 停服后手动删除原数据目录下所有
*.wt文件(⚠️仅限维护窗口且确认无其他进程访问) - 重启
mongod,再用mongorestore --gzip /tmp/gridfs-backup重建——此时 WiredTiger 以全新文件写入,无碎片,磁盘占用最小
全程需预留 ≥ 当前数据库总大小的额外磁盘空间,否则会失败。托管服务(如 Atlas)可能禁止或延迟 compact 生效,务必查平台文档确认权限。











