1. 问题现象:删了数据为何磁盘没瘦身?
当我们在MongoDB中执行db.collection.deleteMany({})
或删除整个集合时,会发现磁盘空间并未立即释放。这就像把衣柜里的衣服扔进垃圾桶,但垃圾桶还在原地占着空间。例如:
// 示例1:删除日志集合中30天前的数据(MongoDB 5.0)
db.access_log.deleteMany({
"timestamp": { $lt: new Date(Date.now() - 30*24*60*60*1000) }
});
执行后通过db.stats()
查看存储情况:
{
"db" : "logs",
"collections" : 12,
"dataSize" : 104857600, // 剩余数据量100MB
"storageSize" : 536870912 // 实际占用空间512MB
}
明明删除了70%的数据,存储空间却纹丝不动。这种"虚胖"现象主要源于MongoDB的存储设计机制。
2. 技术原理:存储引擎的"衣柜管理术"
MongoDB的WiredTiger存储引擎采用写时复制(Copy-on-Write)机制。当执行删除操作时:
- 数据块被标记为"可覆盖"
- 新数据优先写入空白区域
- 原存储空间保持"占位"状态
这种设计带来了两个关键特性:
- 预分配机制:默认预分配数据文件(如
collection-123.wt
) - 碎片化存储:删除操作导致存储碎片,类似硬盘的"文件碎片"
3. 解决方案实战:四步回收空间
3.1 方案1:全量修复(适合小型数据库)
// 示例2:修复数据库(MongoDB 4.4+)
use admin
db.runCommand({
repairDatabase: 1,
preserveUUID: true // 保持集合UUID不变
});
执行效果:
- 重建所有集合文件
- 需要剩余空间=原数据库大小×2
- 生产环境慎用(会阻塞读写)
3.2 方案2:集合压缩(推荐方案)
// 示例3:压缩订单集合(MongoDB 5.0+)
db.runCommand({
compact: 'orders',
force: true, // 主节点强制执行
paddingFactor: 1.0 // 取消预留空间
});
参数说明:
paddingFactor
:调整文档增长预留空间(1.0表示不预留)force
:在主节点直接执行
3.3 方案3:副本集滚动维护(生产环境推荐)
for member in rs.conf().members:
mongosh --host $member --eval """
rs.stepDown(300);
db.adminCommand({ compact: 'sensor_data' });
sleep(10000); // 等待压缩完成
"""
3.4 方案4:分片集群处理
// 示例5:分片集群压缩(MongoDB 5.0+)
sh.getShards().forEach(function(shard){
var conn = new Mongo(shard.host);
conn.getDB('admin').runCommand({
compact: 'customer_profiles',
shardName: shard._id
});
});
4. 关联技术:存储引擎的隐藏技能
WiredTiger的压缩参数配置:
# mongod.conf
storage:
wiredTiger:
engineConfig:
cacheSizeGB: 2
journalCompressor: snappy # 日志压缩算法
collectionConfig:
blockCompressor: zstd # 集合数据压缩算法
支持的三级压缩:
- 字段级压缩(Per-field)
- 文档级压缩(Per-document)
- 块级压缩(Per-block)
5. 应用场景分析
场景类型 | 推荐方案 | 典型业务案例 |
---|---|---|
开发测试环境 | 全量修复 | 本地功能测试数据库 |
中小型生产库 | 定时集合压缩 | 电商订单表按月归档 |
大型分片集群 | 分片滚动维护 | 物联网设备数据存储 |
超高频写入场景 | 调整paddingFactor参数 | 实时日志分析系统 |
6. 技术方案优缺点对比
方案 | 优点 | 缺点 |
---|---|---|
全量修复 | 彻底解决碎片问题 | 需要双倍磁盘空间 |
集合压缩 | 精准控制目标集合 | 主节点性能抖动 |
副本集滚动维护 | 不影响业务连续性 | 维护窗口期较长 |
分片处理 | 适合超大规模数据 | 需要协调多个分片节点 |
7. 操作注意事项
- 备份先行:执行前必须进行
mongodump
或文件系统快照 - 避开高峰:选择业务低峰期执行压缩操作
- 监控指标:
// 示例6:实时监控压缩进度 db.currentOp().inprog.forEach(op => { if(op.command && op.command.compact) { printjson(op.progress); } });
- 分片集群需要先平衡器
8. 最佳实践总结
- 预防优于治理:合理设计文档结构,避免频繁增删改
- 定期维护:建议每月执行一次碎片率检查
// 示例7:碎片率计算 var stats = db.orders.stats(); var fragmentation = (stats.storageSize / stats.size).toFixed(2); print('当前碎片率:', fragmentation * 100 + '%');
- 容量规划:保持至少30%的磁盘空闲空间
- 版本升级:MongoDB 5.0+的压缩效率提升40%