真正需清理碎片的表必须同时满足:DATA_FREE持续>100MB且占总大小超15%、平均行长远超实际、慢查询中Handler_read_next高且key_len异常偏小;否则盲目执行反而引发锁表、主从延迟与IO浪费。

OPTIMIZE TABLE 和 ALTER TABLE ... ENGINE=InnoDB 不是“一跑就快”的银弹,它们只在真实存在严重碎片、且满足硬性条件时才有效;盲目执行反而锁表、拖慢主从、浪费磁盘IO。
怎么确认表真有影响性能的碎片?
别信“删了很多数据所以肯定有碎片”这种直觉。真正要盯的是三个指标组合:
-
DATA_FREE持续 > 100MB 且 占DATA_LENGTH + INDEX_LENGTH超过 15%(例如表总大小 2GB,DATA_FREE长期 > 300MB) - 计算平均行长:
SELECT data_length / table_rows FROM information_schema.tables,结果远超业务实际(如订单表字段加起来才 200 字节,却算出 2.1KB) - 慢查询里频繁出现
Handler_read_next高 +key_len明显小于索引定义长度(比如INDEX idx(a,b),但EXPLAIN显示key_len=5),说明二级索引页稀疏
为什么 OPTIMIZE TABLE 跑完 .ibd 文件大小没变?
这不是命令失败,而是根本没触发空间回收。常见卡点:
-
innodb_file_per_table是OFF:查SELECT @@innodb_file_per_table,返回0就关了;此时所有表共用ibdata1,OPTIMIZE TABLE只能内部复用,无法还空间给操作系统 - 有长事务未提交:
SHOW PROCESSLIST里看到状态为Waiting for table flush,必须等它结束或KILL - 磁盘临时空间不足:重建过程需额外空间 ≥ 当前
.ibd大小;若只剩 15GB,而表文件 20GB,会卡死或静默失败 - 用了
ROW_FORMAT=COMPRESSED:MySQL 5.7 某些小版本对压缩表OPTIMIZE静默跳过,不报错也不生效
大表(>50GB)该用什么方式整理?
直接 OPTIMIZE TABLE 或 ALTER TABLE ... ENGINE=InnoDB 在生产环境风险太高。优先选:
-
pt-online-schema-change --alter="ENGINE=InnoDB" D=your_db,t=your_table --execute:通过影子表+触发器实现零锁表,但要求磁盘有 ≥ 当前表两倍空间 - MySQL 8.0+ 可用
ALTER TABLE your_table ENGINE=InnoDB ALGORITHM=INPLACE LOCK=NONE(需无全文索引、无虚拟列等限制) - 如果只是想释放空间而非强求性能提升,可先
SET SESSION sort_buffer_size = 268435456(256MB)再执行,加速排序阶段
最容易被忽略的陷阱
碎片清理不是越勤越好。频繁重建会放大随机IO压力,在SSD上还会加速磨损;更关键的是:OPTIMIZE TABLE 生成一个巨型 ALTER TABLE ... FORCE binlog,从库重放可能卡顿数小时——你以为主库快了,其实延迟爆表了。真正该动手的,是那些 DATA_FREE 持续增长、且慢查询特征明确匹配碎片表现的表,其他一律观望。



















