MyISAM表空洞是设计行为而非bug,需用OPTIMIZE TABLE物理清理;concurrent_insert=2仅绕过空洞复用、缓解写入延迟,但不消除空洞;Data_free恒为0,真实空洞须通过Data_length与AVG_ROW_LENGTH×TABLE_ROWS对比判断。

MyISAM表在并发读写时产生的“空洞”不是bug,而是设计行为;它不会阻止读取,但会拖慢写入和后续INSERT,且必须用OPTIMIZE TABLE物理清理——没有其他方式能真正回收.MYD文件空间。
为什么concurrent_insert=2能缓解但不能消除空洞
MyISAM默认只在无空洞(即Data_free = 0)时允许并发INSERT,而concurrent_insert=2强制所有新行追加到.MYD文件末尾,绕过空洞复用逻辑:
- 避免因空洞大小不匹配导致的随机寻址开销
- 降低写锁持有时间,提升并发读的响应速度
- 但空洞依然存在——DELETE标记的行没被物理移除,
Data_length不变,.MYD文件体积照旧 - 长期运行后,空洞仍会堆积,只是新数据不往里塞了
确认空洞是否真实影响性能的唯一方法
别信Data_free字段:MyISAM的Data_free始终为0,它不反映实际空洞。唯一可靠判断方式是对比:
- 查
SHOW TABLE STATUS LIKE 't'\G中的Data_length - 估算真实数据量:
AVG_ROW_LENGTH × TABLE_ROWS(注意TABLE_ROWS是采样值,误差可能达40%) - 若
Data_length比估算值大50%以上,基本可断定存在显著空洞
OPTIMIZE TABLE执行前必须检查的三件事
这个命令会重写整个.MYD和.MYI文件,失败代价高:
- 磁盘剩余空间 ≥
Data_length + Index_length(查SHOW TABLE STATUS结果),否则直接报错ERROR 1030 (HY000): Got error 28 from storage engine -
SELECT @@read_only必须返回0,只读模式下命令会被拒绝 - 主从架构中,务必先在从库停SQL线程:
STOP SLAVE SQL_THREAD;,再执行,否则可能卡住复制
高频DELETE场景下,OPTIMIZE TABLE只是止痛药
如果每周都要跑一次OPTIMIZE TABLE,说明MyISAM已不适合当前负载:
- ARCHIVE引擎更适合日志类只增表,压缩率高、DELETE极少,天然规避空洞
- 用表名分片模拟分区(如
t_202606、t_202607),旧表直接DROP TABLE,零碎片、秒级释放空间 - 迁移到InnoDB并启用
innodb_file_per_table=ON,再用ALTER TABLE t ENGINE=InnoDB重建——虽仍需锁,但比反复OPTIMIZE更可控
真正容易被忽略的是:MyISAM没有后台Purge线程,空洞一旦产生,就只能靠人工OPTIMIZE TABLE硬回收;它不像InnoDB那样能异步清理标记删除行。


















