随着业务的持续运行,数据库中的数据会经历频繁的增删改操作。久而久之,MySQL 表,尤其是那些体量庞大的核心业务表,就容易产生大量的“碎片”。这些碎片不仅会白白占用宝贵的磁盘空间,更关键的是,它们会拖慢全表扫描等操作的效率,影响查询性能。因此,定期检查和整理表碎片,是数据库运维中一项重要的日常维护工作。今天,我们就来详细聊聊如何在 MySQL 中实践这项操作。
如何判断哪些表需要整理?
目前,大多数生产环境中的 MySQL 都启用了 innodb_file_per_table 参数。这意味着每个 InnoDB 表都拥有自己独立的表空间文件,即 表名.ibd。当表内产生大量碎片时,对应的 .ibd 文件会变得“虚胖”,实际数据量可能没那么大,但占用的磁盘空间却很多。回收碎片后,这个文件通常会显著“瘦身”。
那么,我们如何量化“碎片”呢?答案藏在系统表 information_schema.TABLES 的 DATA_FREE 字段里。这个字段记录的是表中可用的、未使用的空闲空间(单位:字节)。通常,DATA_FREE 的值越大,意味着该表的碎片化程度可能越严重。

对于变动频繁或数据量巨大的表,定期查看其 DATA_FREE 大小是一个好习惯。我们甚至可以通过查询系统表,批量筛选出那些“碎片大户”或“碎片率”过高的表。下面这几条 SQL 应该能覆盖你大部分的分析场景:
# 查看指定数据库和表的详细信息(包含碎片大小)
select
table_schema as '数据库',
TABLE_NAME as '表名',
sys.FORMAT_BYTES(data_length) as '数据容量',
sys.FORMAT_BYTES(index_length) as '索引容量',
sys.FORMAT_BYTES(data_length+index_length) as '总容量' ,
sys.FORMAT_BYTES(DATA_FREE) as ‘碎片大小’
from information_schema.tables where TABLE_SCHEMA = ‘db_name’ and TABLE_NAME = ‘tb_name’;
# 按碎片绝对大小降序排列,找出前20名
SELECT t.TABLE_SCHEMA,
t.TABLE_NAME,
t.DATA_FREE,
sys.FORMAT_BYTES(DATA_LENGTH+INDEX_LENGTH) as ‘总容量’ ,
sys.FORMAT_BYTES(DATA_FREE) as ‘碎片大小’
FROM information_schema.tables t
WHERE
t.table_schema NOT IN (
‘mysql’,
‘information_schema’,
‘performance_schema’,
‘sys’
)
AND t.table_type = ‘BASE TABLE’ ORDER BY `DATA_FREE` DESC LIMIT 20
# 筛选碎片率大于30%的表(碎片率 = DATA_FREE / (DATA_LENGTH + INDEX_LENGTH))
select
table_schema as ‘数据库’,
TABLE_NAME as ‘表名’,
sys.FORMAT_BYTES(DATA_LENGTH+INDEX_LENGTH) as ‘总容量’ ,
sys.FORMAT_BYTES(DATA_FREE) as ‘碎片大小’,
(DATA_FREE / (data_length + index_length)) AS ‘碎片率’
FROM information_schema.tables t
WHERE
t.table_schema NOT IN (
‘mysql’,
‘information_schema’,
‘performance_schema’,
‘sys’
) AND t.table_type = ‘BASE TABLE’ and (DATA_FREE / (data_length + index_length)) > 0.3
利用以上查询,你可以轻松定位到需要重点关照的表。是优先处理碎片总量最大的,还是碎片率最高的?这可以根据你的磁盘空间状况和性能优化目标来决定。确定目标后,我们就可以进入实际操作阶段了。
执行碎片回收操作
对于 InnoDB 引擎的表,主流的碎片整理方法是以下两种:
OPTIMIZE TABLE table_name;
ALTER TABLE table_name ENGINE = InnoDB;
它们都能达到回收空间、整理碎片的目的,但底层逻辑和侧重点略有不同。
OPTIMIZE TABLE 命令会重建表数据和索引,更新统计信息,并释放聚簇索引中未使用的空间。这个过程可以有效减少表的物理存储大小,并提升后续访问的I/O效率。在 MySQL 5.6 及以后版本,它对常规表和分区表使用 Online DDL 机制,这意味着在大部分操作时间内,表仍然可以正常进行读写(DML操作),只在准备和提交阶段有极短暂的元数据锁。这大大降低了对业务的影响。
而 ALTER TABLE ... ENGINE = InnoDB 的本意是重新设置表的存储引擎。在引擎本身已是 InnoDB 的情况下执行此操作,同样会触发一次表的重建,从而附带实现空间回收。从 5.6 版本开始,这个操作也支持 Online DDL。
如何选择?
简单来说,OPTIMIZE TABLE 是官方更“正统”的碎片整理命令,语义清晰,且适用于多种存储引擎。而 ALTER TABLE ... ENGINE=InnoDB 则更像是通过“重建”表结构带来的一个副作用。在日常维护中,使用 OPTIMIZE TABLE 是更常见和推荐的做法。
操作前的关键注意事项
虽然这两个操作都号称“Online”,但并不意味着可以随意在业务高峰时段执行。尤其是面对几百GB甚至TB级别的大表时,重建过程仍然需要相当长的时间,会消耗大量CPU和I/O资源。因此,务必选择业务低峰期(如深夜)进行操作。
另一个至关重要且容易被忽视的点是:确保磁盘有足够的剩余空间。表重建过程中,MySQL 会在同一目录下创建临时文件来存放新数据。这意味着,在最极端的情况下,你的磁盘需要同时容纳原始表文件(例如200GB)和正在构建的新临时文件(可能接近200GB)。即使你预估能回收50GB空间,操作期间也可能需要近400GB的峰值空间。通常的建议是,执行此类操作前,确保磁盘剩余空间至少大于待整理表的大小。如果空间不足,操作将会失败。
总结
定期检查和整理 MySQL 表的碎片,是一项投入产出比很高的数据库运维工作。它不仅能帮你节省磁盘空间,更能通过优化数据存储布局,提升查询性能,让数据库运行得更顺畅。建议将碎片检查纳入日常或每周的巡检清单,在做好充分评估和备份的前提下,于业务低峰期执行整理操作。
希望这篇结合了原理和实操的指南能对你有所帮助。如果你对更多 MySQL 或数据库相关的深度优化话题感兴趣,欢迎来云栈社区交流讨论。