OPTIMIZE TABLE是MyISAM碎片整理的唯一有效方式,因其会重建.MYD/.MYI文件、排序索引、合并空闲页并释放空间;而ALTER TABLE ENGINE=MyISAM仅重声明引擎,不触发物理重建,无法清理碎片。

MyISAM表碎片为什么必须用OPTIMIZE TABLE而不是ALTER TABLE ENGINE=MyISAM
因为OPTIMIZE TABLE对MyISAM是原生命令,会真正重建数据文件(.MYD)和索引文件(.MYI),排序索引、合并空闲页、释放磁盘空间;而ALTER TABLE ENGINE=MyISAM只是“重声明”引擎,不触发物理重建,无法清理碎片。
常见错误现象:ALTER TABLE log_table ENGINE=MyISAM执行完,Data_free没变小,表文件大小几乎不变——这不是操作失败,而是根本没做碎片整理。
-
OPTIMIZE TABLE期间,MyISAM表会被**完全锁定(写锁)**,所有INSERT/UPDATE/DELETE阻塞,SELECT可继续(但可能被长事务拖慢) - 执行后
Data_length和Index_length通常明显下降,Data_free归零或极小 - 不要在业务高峰执行;大表建议搭配
myisamchk --recover离线修复(需停服务)
如何批量识别该优化的MyISAM表(避免盲目执行)
不是所有MyISAM表都值得优化。碎片率低的小表优化收益微乎其微,反而增加IO压力。重点筛出:碎片空间 ≥ 10MB 且碎片率 ≥ 20% 的表。
在phpMyAdmin或宝塔终端中执行以下查询:
SELECT table_schema, table_name, ROUND(data_free / 1024 / 1024, 2) AS data_free_mb, ROUND((data_free / NULLIF(data_length + index_length, 0)) * 100, 2) AS frag_pct FROM information_schema.tables WHERE engine = 'MyISAM' AND data_free > 10 * 1024 * 1024 AND (data_free / NULLIF(data_length + index_length, 0)) >= 0.2;
结果为空?说明当前没有高碎片MyISAM表,不用急着跑OPTIMIZE TABLE。
- 注意
NULLIF防止除零错误;MyISAM的data_free字段真实反映已分配但未用的字节数 - 如果某张日志表
data_length才2MB但data_free有800MB,基本确定是高频DELETE+INSERT导致严重碎片 - 查出的表名可直接拼成
OPTIMIZE TABLE db_name.table_name;批量执行
OPTIMIZE TABLE执行失败或卡住的典型原因
MyISAM下OPTIMIZE TABLE失败,90%以上和磁盘空间、临时目录权限或表损坏有关。
- 临时目录(通常是
/tmp)剩余空间 Data_length + Index_length)→ 直接报错ERROR 1034 (HY000): Incorrect key file... - 表文件(
.MYD或.MYI)损坏 → 执行中报error: record delete count is not correct,此时应改用myisamchk --safe-recover - 并发写入未停干净 → 卡在“Waiting for table flush”,需先确认无长连接正在操作该表(
SHOW PROCESSLIST) - 宝塔面板9.x默认禁用
OPTIMIZE按钮,不是功能缺失,而是防止用户误点锁表影响线上服务
为什么不能把MyISAM碎片整理做成每日定时任务
碎片整理不是“越勤快越好”。MyISAM的OPTIMIZE TABLE本质是复制全表+重建索引,IO密集、耗时长、锁表久。频繁执行反而损害稳定性。
合理节奏取决于表变更频率:
- 日志类表(如
access_log),每天DELETE旧数据+INSERT新数据 → 建议每周日凌晨执行一次 - 配置类小表(
sys_config),月均更新 - 一旦发现某表
OPTIMIZE后第二天Data_free又飙升到几百MB,说明设计有问题:要么缺少合适索引导致大量随机写,要么该换InnoDB了
真正容易被忽略的是:MyISAM表没有行级锁,碎片恶化后全表扫描性能断崖下跌,但监控指标(如QPS、慢查数)未必立刻报警——得盯紧information_schema.TABLES里的data_free和avg_row_length变化趋势。


















