myisam静态表count(*)快是因为直接读取.myi文件头中持久化的rec_count元数据,时间复杂度o(1),但该值非实时、并发写入下可能不一致,且repair或optimize后常被重置导致首次查询需全表扫描。

MyISAM静态表的物理结构让定位更直接
MyISAM静态表(FIXED格式)所有字段都是定长类型(比如CHAR、INT),每条记录长度完全一致。这意味着数据库可以直接用「起始位置 + 记录长度 × 行号」算出任意记录的磁盘偏移量,跳过中间所有数据块,实现O(1)随机访问。
对比动态表(DYNAMIC)或InnoDB:前者要遍历变长记录头找实际长度,后者需走B+树索引再回表查聚簇数据——多一层间接寻址。静态表省掉这些步骤,尤其在SELECT * FROM t WHERE id = ?这类主键等值查询中,性能差异肉眼可见。
- 静态表必须全字段为定长;一旦混入
VARCHAR、TEXT或BLOB,MyISAM会自动降级为动态表 -
CHAR字段尾部空格会被MySQL自动trim,如果业务依赖原始空格,得改用BINARY或VARBINARY - 静态表文件(
.MYD)更容易被操作系统页缓存命中,配合key_buffer_size能进一步放大优势
为什么静态表COUNT(*)快但不可信?
MyISAM静态表把总行数存在.MYI索引文件头里,SELECT COUNT(*) FROM t直接读这个值,不扫数据文件。但这只是元数据快照,不是实时计数。
并发INSERT时,MyISAM不加锁更新该计数器,INSERT和COUNT(*)可能看到不同版本;更麻烦的是REPAIR TABLE或OPTIMIZE TABLE后,计数器常被重置为0,后续第一次COUNT(*)被迫全表扫描——你根本不知道它什么时候开始“假快”。
- 线上环境别依赖
COUNT(*)结果,尤其在有写入的场景 - 若真需要近似行数,
SHOW TABLE STATUS LIKE 't'里的Rows字段更稳定(仍是估算) - 精确计数优先走覆盖索引:
SELECT COUNT(id) FROM t WHERE id > 0(要求id非NULL)
静态表修复失败风险比动态表更低
崩溃后,MyISAM静态表的.MYD和.MYI文件损坏概率更低。因为定长记录没有指针跳转、无碎片链表,myisamchk -r修复时能更可靠地对齐记录边界。
但注意:这不等于“不会坏”。断电导致.MYD写到一半、.MYI索引未同步更新,仍会报Incorrect key file for table。此时myisamchk -r可能静默跳过损坏块,丢数据却不报错。
- 修复前务必先备份
.MYD和.MYI文件 - 不要在运行中的mysqld上直接执行
myisamchk,否则可能引发二次损坏 - 静态表虽易恢复,但无法规避MyISAM本身无崩溃安全机制的根本缺陷
静态表的存储空间代价经常被低估
静态表按最大宽度分配每行空间。比如CHAR(255)存"abc",也占255字节;而动态表只存实际3字节 + 1字节长度头。一张百万行的用户表,若含多个大CHAR字段,静态表可能比动态表多占30%~50%磁盘。
更大的隐患是内存浪费:key_buffer_size缓存的是索引块,但OS页缓存会把整个.MYD文件尽可能加载进内存——静态表体积大,挤占其他进程可用内存,反而拖慢整体IO。
- 用
SHOW TABLE STATUS对比Data_length和Index_length,确认是否真节省了I/O - 混合使用:关键查询字段用
CHAR保静态特性,描述类字段改VARCHAR降体积 - MySQL 8.0已移除MyISAM全文索引优势,别为FTS硬凑静态表
真实场景里,静态表的“快”只在极窄条件下成立:纯读、低QPS、数据量适中、且你能接受修复时的数据静默丢失。一旦写入频率超过每秒几次,或者表大小突破几GB,它的优势就迅速被锁争用和空间膨胀吞没。











