真正该动手整理的信号是DATA_FREE/(DATA_LENGTH+INDEX_LENGTH)持续超25%且伴缓冲池读取异常或执行计划失真;碎片率需结合外部空闲页、物理文件大小差值及页填充率三类指标综合判断。

DATA_FREE 高不等于必须整理,真正该动手的信号是:DATA_FREE / (DATA_LENGTH + INDEX_LENGTH) 持续超过 25% 且伴随缓冲池读取异常或执行计划失真。
怎么看碎片率才靠谱?别只盯着 DATA_FREE
直接查 information_schema.TABLES 的 DATA_FREE 是最常用手段,但它只反映“外部空闲页”,漏掉大量页内碎片(比如一页 16KB,只存了 3KB 数据)。真正要判断是否值得动,得结合三类指标:
- 碎片率公式:用
SELECT table_name, ROUND((data_free / NULLIF(data_length + index_length, 0)) * 100, 2) AS frag_pct算出百分比,>25% 才算警戒线 - 物理文件对比:执行
du -h /var/lib/mysql/db_name/tbl_name.ibd,再查SUM(DATA_LENGTH + INDEX_LENGTH),若前者比后者大 50% 以上,说明碎片已实质性膨胀 - 页填充率佐证:查
sys.schema_table_statistics或关联INNODB_SYS_TABLES和INNODB_SYS_INDEXES,看FILL_FACTOR近似值,
OPTIMIZE TABLE 为什么经常白忙一场?
它对 InnoDB 不是“碎片整理命令”,而是隐式执行 ALTER TABLE ENGINE=InnoDB,效果取决于硬条件:
- 必须
innodb_file_per_table = ON(默认开启,但旧实例可能关着);否则所有表共用ibdata1,OPTIMIZE压根不会缩小磁盘文件 - 表不能含全文索引、虚拟列、外键约束——MySQL 8.0+ 会自动退化为
ALGORITHM=COPY,全程锁表 - 即使满足条件,若碎片主要来自页内空洞(而非整页空闲),
DATA_LENGTH几乎不变,.ibd文件也缩不回来 - 执行时需额外 ≈ 1.2 倍当前表大小的磁盘空间,OOM 风险高,尤其没调大
innodb_online_alter_log_max_size
更安全的替代方案怎么选?
优先绕过黑盒行为,用显式、可控的 DDL:
- 小表(ALTER TABLE t ENGINE=InnoDB,比
OPTIMIZE TABLE更透明,还能顺手加其他操作(如改ROW_FORMAT) - 大表(>10GB)或生产环境:用
pt-online-schema-change --alter="ENGINE=InnoDB" --execute,全程无锁,但需提前部署 Percona Toolkit - 只想更新统计信息(解决慢查询误选索引):用
ANALYZE TABLE t,零开销、不碰数据页,和碎片无关但常被误当“整理” - 彻底重建(绕过所有引擎限制):
mysqldump导出 +mysql导入,最干净,但需停写或业务低峰
容易被忽略的底层前提
所有操作前,必须确认三件事:
- 当前
innodb_file_per_table确实是ON(查SHOW VARIABLES LIKE 'innodb_file_per_table'),否则一切物理收缩都是徒劳 - 表的
ROW_FORMAT是DYNAMIC或COMPACT,Antelope文件格式已不支持多数在线 DDL - 你监控的不是单个
DATA_FREE数值,而是它在业务周期内的变化趋势——稳定增长且同步出现innodb_buffer_pool_reads上升、innodb_buffer_pool_read_requests下降,才是真实信号


















