mongodb碎片过大导致磁盘告警
故障上报
- 2026.01.22,开发同事反馈已对4张大表清理了1400w条数据,但数据库并没有变小
//.查看版本
> db.version();
3.4.23
//.查看数据库大小
> show dbs;
Device 184.011GB #.这个库有184G
admin 0.000GB
local 0.000GB
//.查看实例的存储引擎
> db.serverStatus().storageEngine.name
wiredTiger
//.切换数据库
> use Device
switched to db Device
//.查看当前库 dataSize 仅仅 33G,但 storageSize 却有 182GB
> db.stats(1048576);
{
"db" : "Device",
"collections" : 32,
"views" : 0,
"objects" : 24401694,
"avgObjSize" : 1455.8131190400143,
"dataSize" : 33.08458836004138, #.dataSize 实际存储的数据总大小(未压缩),仅仅 33G
"storageSize" : 182.49642944335938, #.storageSize 在磁盘上实际占用的空间大小,却有 182GB
"numExtents" : 0,
"indexes" : 40,
"indexSize" : 1.5138969421386719,
"ok" : 1
}
//.查看当前库所有表的行数及大小
db.getCollectionNames().forEach(c => {
const stats = db[c].stats();
print(`${c}: 行数=${(stats.count)}, 数据=${(stats.size/1024/1024).toFixed(2)}MB, 存储=${(stats.storageSize/1024/1024).toFixed(2)}MB, 碎片率=${(stats.storageSize/stats.size).toFixed(2)}`);
});
//.这里只显示最大的几张表
FlowSideReturnData: 行数=2704175, 数据=5808.47MB, 存储=53453.50MB, 碎片率=9.20
FlowSideSendData: 行数=2707721, 数据=2135.50MB, 存储=15102.12MB, 碎片率=7.07
OAIDSRDevice1: 行数=1799997, 数据=940.99MB, 存储=994.14MB, 碎片率=1.06
OriginalData: 行数=2210318, 数据=17554.13MB, 存储=101064.43MB, 碎片率=5.76
OriginalSendData: 行数=2712597, 数据=1560.31MB, 存储=10840.95MB, 碎片率=6.95
方案1: 回收空间碎片
- 解决方案1:对碎片率超过1.5的大表执行compact回收空间碎片(建议碎片率在1.2以下,优化过程会有短暂性能影响),而执行 compact 的前提是实例的存储引擎为WiredTiger。
注1:MongoDB 4.4 之前的版本执行compact命令会导致集合所属的数据库被锁定,且该数据库的读写操作将被阻塞。而 MongoDB 4.4及之后的版本compact命令不再阻塞业务读写,但执行过程中可能会影响性能,建议在业务低峰期分批处理。
注2:从 MongoDB 版本 8.0 开始,引入了一项名为 autoCompact 的新功能。该功能在后台运行,以识别空闲空间并回收这些空间,从而优化存储效率
//.切换数据库
> use Device
switched to db Device
//.对碎片率超过1.5的大表进行压缩优化
db.runCommand({compact: 'FlowSideReturnData', force: true}); //.耗时 162 秒,大小 184.011GB -> 173.272GB
db.runCommand({compact: 'FlowSideSendData', force: true}); //.耗时 171 秒,大小 173.272GB -> 160.374GB
db.runCommand({compact: 'OriginalData', force: true}); //.耗时 1943 秒,大小 160.374GB -> 83.314GB
db.runCommand({compact: 'OriginalSendData', force: true}); //.耗时 110 秒,大小 83.314GB -> 74.231GB
//.附统计耗时的脚本
const startTime = new Date();
db.runCommand({compact: 'OriginalSendData', force: true});
const endTime = new Date();
const elapsedTime = endTime - startTime;
print(`执行耗时: ${elapsedTime}ms (${(elapsedTime/1000).toFixed(2)}秒)`);
//.对所有集合运行compact,耗时 2分钟,大小 74.231GB -> 39.632GB
db.getCollectionNames().forEach(coll => {print("收缩集合: " + coll); db.runCommand({compact: coll});});
//.查看当前库 dataSize 与 storageSize 分别为 32GB、40GB,基本相差不大了
> db.stats(1048576);
{
"db" : "Device",
"collections" : 32,
"views" : 0,
"objects" : 24093632,
"avgObjSize" : 1427.9587332453655,
"dataSize" : 32810.890417099,
"storageSize" : 40154.11328125,
"numExtents" : 0,
"indexes" : 40,
"indexSize" : 428.6953125,
"ok" : 1
}
方案2: 导出与导入
- 解决方案2:通过 mongodump 和 mongorestore 迁移数据,实现彻底清理,该方案效果最好,但需停机以确保备份与一致性
#.1.对特定库进行备份
cd /d d:\sqldata\mongodb27017\bin
mongodump -h 127.0.0.1 --port 27017 -uroot -p123456 --authenticationDatabase admin -d Device -o d:/backup/20260122/
#.2.删除特定库
mongo -uroot -p123456 --authenticationDatabase admin --port 27017
> use Device;
> db.dropDatabase();
#.3.对特定库进行恢复
cd /d d:\sqldata\mongodb27017\bin
mongorestore --port 27017 -uroot -p123456 --authenticationDatabase admin -d Device --drop d:/backup/20260122/Device/
#.4.登陆测试
cd /d d:\sqldata\mongodb27017\bin
mongo -uroot -p123456 --authenticationDatabase admin --port 27017
方案3: TTL索引
- 解决方案3:时效性数据推荐使用TTL索引(Time to Live),对一个date类型的单列配置过期属性(时间戳或clock则不行)可实现自动删除过期数据。这可以减少碎片产生,并保持数据库的整洁
// 自动删除aaa表中insert_time字段超过7天的文档
db.aaa.createIndex({"insert_time": 1},{expireAfterSeconds: 60*60*24*7})
方案4: 副本集重新同步
- 解决方案4:如果是副本集,可以尝试如下步骤:
停止从节点
删除从节点数据目录
重新启动从节点,让其自动重新同步
主从切换后重复操作