innodb_file_per_table=OFF是碎片无法释放的根源,此时所有表数据写入共享表空间ibdata1,OPTIMIZE TABLE或ALTER TABLE ENGINE=InnoDB均无法将空间返还操作系统,必须停机导出、初始化新实例并配置为ON才能解决。

innodb_file_per_table=OFF 是碎片无法释放的根源
如果你执行了 OPTIMIZE TABLE 或 ALTER TABLE t ENGINE=InnoDB,但磁盘空间没降、Data_free 依然很大,大概率是 innodb_file_per_table 关闭了。此时所有表数据都写进共享表空间 ibdata1,无论怎么重建单个表,空间都不会返还给操作系统。
确认方式:SHOW VARIABLES LIKE 'innodb_file_per_table'; —— 返回 OFF 就必须先停机处理,不能在线修复。
- 已上线系统:无法直接开启该参数,需导出全库 → 初始化新实例(配置中显式设为
ON)→ 导入 - 新实例部署前务必检查 my.cnf,加入
innodb_file_per_table=ON,这是后续所有碎片控制的前提 -
ibdata1一旦膨胀,只能重建,不能 shrink;不要试图删除它后重启 MySQL,会导致实例无法启动
OPTIMIZE TABLE 对 InnoDB 实际做了什么
它不是“整理页内空洞”,而是触发一次完整表重建:ALTER TABLE t FORCE,即新建临时表、拷贝数据、rename、删旧表。所以它有效,但代价高。
常见误判现象:SHOW TABLE STATUS 显示 Data_free 没变小,甚至 data_length 变大了——这往往是因为原表行格式是 COMPACT,而重建后默认用了 DYNAMIC,导致元数据开销略增,不代表失败。
- 重建过程会加
S锁(可读不可写),大表可能阻塞写入数分钟到数小时 - 需要额外磁盘空间 ≥ 当前
data_length + index_length,否则直接报错ERROR 1114 (HY000): The table is full - 不会重置
AUTO_INCREMENT值(InnoDB 行为),但会重写聚簇索引,对主键顺序有影响
比 OPTIMIZE 更可控的重建方式:显式 ALTER TABLE
用 ALTER TABLE t ENGINE=InnoDB 替代 OPTIMIZE TABLE,语义更明确,也方便加参数控制行为。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
例如想尽量减少锁时间:ALTER TABLE t ENGINE=InnoDB, ALGORITHM=INPLACE, LOCK=NONE; —— 但该组合有严格前提:不能有全文索引、虚拟列、主键变更等。
- 若报错
ALGORITHM=INPLACE is not supported,说明涉及不支持的 DDL,自动退回到ALGORITHM=COPY(等价于 OPTIMIZE) - 想提升页利用率:先改行格式
ALTER TABLE t ROW_FORMAT=DYNAMIC;,再重建,能更好压缩变长字段空洞 - 从库上慎用:
binlog_format=STATEMENT时,OPTIMIZE会被记录为完整语句复制,可能引发从库执行卡住
真正该清理碎片的信号,不是 Data_free 大小
Data_free 高 ≠ 必须清理。InnoDB 中它只是“当前表空间内未使用的页”,这些页后续 INSERT 可能复用。盲目优化反而增加 IO 和锁风险。
判断是否真要干预,看这三个指标:
-
SELECT (data_length + index_length) / table_rows AS avg_row_size FROM information_schema.tables—— 若结果远超实际字段总和(比如业务平均 200 字节,却算出 1.8KB),说明存在大量空洞 - 历史
DELETE比例 >30%,且之后几乎没有INSERT/UPDATE回填 - 慢查询中频繁出现
Using temporary; Using filesort,且EXPLAIN显示扫描行数远超返回行数(B+ 树深度或空洞影响了范围扫描效率)
碎片率超过 30%(Data_free / (data_length + index_length))才值得动手,而不是看到几 MB 就跑命令。










