在回答这个问题之前,先了解一下 MySQL InnoDB 存储引擎是如何回收磁盘空间的。

假设有一张表包含 1000 万条记录,删除其中的 800 万条记录,此时 InnoDB 的磁盘空间不会自动释放。要回收磁盘空间,需要执行以下命令:

ALTER TABLE tableName ENGINE=InnoDB;
  • 1.


MongoDB 的 WiredTiger 存储引擎也有类似的行为。要回收磁盘空间,可以执行以下命令:

use dbName
// 在副本集 primary 上执行需要加 force 选项
db.runCommand({compact: "collectionName", force: true})
  • 1.
  • 2.
  • 3.


下面通过一个测试用例来实际演示这一过程。

首先,插入 100 万条随机生日数据到 MongoDB 的 demo 集合中。

function getRandomDate() {
    const start = new Date(1950, 0, 1);
    const end = new Date(2005, 11, 31);
    return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()));
}

for (var i = 1; i <= 1000000; i++) {
    db.demo.insert({
        name: "name" + i,
        birthday: getRandomDate()
    });
    if (i % 10000 === 0) {
        print(`Inserted ${i} records`);
    }
}

print("Insertion complete");
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.


接下来,删除所有生日在 1980 年 1 月 1 日之前的记录。

db.demo.count({ birthday: { $lt: new Date("1980-01-01") } });
db.demo.deleteMany({ birthday: { $lt: new Date("1980-01-01") } });
  • 1.
  • 2.


删除操作后,检查 demo 集合可以回收的磁盘空间(单位:字节)。

db.demo.stats().wiredTiger["block-manager"]["file bytes available for reuse"];
  • 1.


最后,执行磁盘空间回收操作。

// 在副本集 primary 上执行需要加 force 选项
db.runCommand({compact: "demo", force: true});
  • 1.
  • 2.


需要注意的是,在 MongoDB 4.4 版本之前,compact 操作会阻塞 CRUD 操作。而在 MongoDB 4.4 及更新的版本中,compact 操作不会阻塞 CRUD 操作,但会阻塞元数据操作,例如删除集合、删除索引和创建新索引。


最后附上统计库里每个集合可回收的空间(GB)脚本

// 使用指定的数据库
use yourDB

// 定义常量
var bytesInGB = 1024 * 1024 * 1024;

// 获取所有集合名称
var cols = db.getCollectionNames();

// 创建一个对象来存储每个集合的可回收空间
var collectionSpace = {};

// 计算总可回收空间
var totalReclaimableSpace = 0;

// 遍历每个集合
cols.forEach(function(col) {
    if(col != 'system.profile') {
        var stats = db[col].stats();
        var reclaimableSpace = stats.wiredTiger["block-manager"]["file bytes available for reuse"];
        
        // 将字节转换为GB并保留两位小数
        var spaceInGB = (reclaimableSpace / bytesInGB).toFixed(2);
        
        // 存储每个集合的可回收空间
        collectionSpace[col] = spaceInGB;
        
        // 累加总可回收空间
        totalReclaimableSpace += reclaimableSpace;
    }
});

// 打印每个集合的可回收空间
print("每个集合可回收的空间(GB):");
for (var col in collectionSpace) {
    print("集合:" + col + " -> 可回收空间(GB): " + collectionSpace[col]);
}

// 打印总可回收空间
print("\n总共可回收空间(GB): " + (totalReclaimableSpace / bytesInGB).toFixed(2));
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.

参考文献: https://www.percona.com/blog/using-compact-in-percona-server-for-mongodb-from-version-4-4/

 https://www.percona.com/blog/how-to-reclaim-disk-space-in-percona-server-for-mongodb/