确认碎片是否真拖慢查询,应先检查INFORMATION_SCHEMA.INNODB_TABLESTATS统计是否失真,再验证innodb_file_per_table是否为ON,最后用ALTER TABLE FORCE替代OPTIMIZE TABLE以避免阻塞。

确认碎片是否真在拖慢查询,而不是统计信息失真
升级后查询变慢,第一反应不是立刻 OPTIMIZE TABLE,而是先看是不是 INFORMATION_SCHEMA.INNODB_TABLESTATS 里的统计值崩了。InnoDB 表空间碎片本身不直接卡点查(WHERE pk = ?),但会让 avg_row_length、data_length 严重偏离真实值,导致优化器误判——比如该走索引却全表扫描,EXPLAIN 显示的 rows 从几千跳到几十万,但表行数根本没变。
验证方法:
- 执行 SELECT (data_length + index_length) / table_rows AS avg_row_size FROM INFORMATION_SCHEMA.TABLES WHERE table_name = 't' AND table_schema = 'db',和你业务里单行实际字节数对比(比如字段加起来才 180 字节,算出来却是 2.3KB);
- 查慢查询日志,重点看有没有频繁出现 Using temporary; Using filesort 且 rows 远大于 filtered * rows 的 SQL。
检查 innodb_file_per_table 是否为 ON,否则 OPTIMIZE 无效
MySQL 升级后默认行为可能变化(尤其从 5.7 升到 8.0.29+),innodb_file_per_table 可能被重置为 OFF。如果它是 OFF,所有表共用 ibdata1,那 OPTIMIZE TABLE 根本不会缩小任何物理文件——它只在内部复用空闲页,ibdata1 永远不会变小,Data_free 也不代表可释放空间。
必须执行:
- SHOW VARIABLES LIKE 'innodb_file_per_table',确认返回值是 ON;
- 如果是 OFF,不能直接改,得先导出数据、删库、改配置、重建库再导入;
- 升级前备份里若含 mysqldump --all-databases,检查 dump 文件开头是否有 SET GLOBAL innodb_file_per_table=ON 类语句,没有就说明旧实例可能长期关着它。
别直接跑 OPTIMIZE TABLE,优先用 ALTER TABLE FORCE
OPTIMIZE TABLE 在 MySQL 8.0 仍默认走 ALGORITHM=COPY,会全程阻塞写入;而 ALTER TABLE t FORCE 语义更明确、行为更可控,MySQL 会自动选最优算法(5.6+ 默认 online DDL),只在最后短暂加 MDL 写锁。
实操建议:
- 对大表,加 WAIT 30 防止被长事务卡死:ALTER TABLE t FORCE, WAIT 30;
- 执行前确认无长事务:SELECT * FROM information_schema.INNODB_TRX WHERE trx_started ;<br>
- 磁盘空间必须 ≥ <code>Data_length + Index_length(查 SHOW TABLE STATUS LIKE 't'),因为重建过程先写新 .ibd 文件,再原子替换;
- 如果表用 ROW_FORMAT=COMPRESSED,FORCE 可能失败,得先 ALTER TABLE t ROW_FORMAT=DYNAMIC 再操作。
升级后碎片问题容易被忽略的三个硬条件
很多升级后“优化无效”,其实是踩中了底层硬约束:
- tmpdir 空间不足:重建表时临时文件写在那里,不是 datadir;
- innodb_fast_shutdown 为 1 或 2:会导致 .ibd 文件无法收缩,必须设为 0 后重启;
- 主从复制场景下,OPTIMIZE 或 FORCE 会产生一个巨型 binlog event,从库 IO 线程可能积压甚至崩溃,得提前调大 slave_parallel_workers 和 relay_log_space_limit。



















