前言
MySQL 5.5 起 InnoDB 就是默认引擎,5.7 里 SHOW ENGINES 输出的 DEFAULT 也还是它:
+--------+---------+------------------------------------------------------------+--------------+
| Engine | Support | Comment | Transactions |
+--------+---------+------------------------------------------------------------+--------------+
| InnoDB | DEFAULT | Supports transactions, row-level locking, and foreign keys | YES |
| MyISAM | YES | MyISAM storage engine | NO |
| MEMORY | YES | Hash based, stored in memory, useful for temporary tables | NO |
+--------+---------+------------------------------------------------------------+--------------+
但老项目里 MyISAM 的表还是能碰到,两者的差别不是"快慢"两个字能概括的。
核心区别
| 对比项 | InnoDB | MyISAM |
|---|---|---|
| 事务 | 支持,有 savepoint | 不支持,语句自动提交 |
| 锁粒度 | 行锁(走索引时)+ 表锁 | 只有表锁 |
| 索引结构 | 聚簇索引,二级索引叶子存主键 | 非聚簇,索引叶子存行地址 |
| 崩溃恢复 | redo log,重启自动回滚/前滚 | 靠 binlog 或 myisamchk,容易不一致 |
| 外键 | 支持 | 不支持 |
| 全文索引 | 5.6 起支持 | 支持 |
COUNT(*) 无条件 |
需要扫描 | 元数据里存了精确行数,直接返回 |
| 存储文件 | tbl.ibd(独立表空间) |
tbl.MYD 数据 + tbl.MYI 索引 |
| 缓存重点 | innodb_buffer_pool_size |
key_buffer_size(只缓存索引) |
差异背后的两个原因
一是索引结构。 MyISAM 的 .MYI 索引和 .MYD 数据是分开的两个文件,索引叶子记的是数据文件的偏移地址,插入时不用移动数据,更新变长字段也方便,代价是一次查询要走两次 IO。InnoDB 的表数据本身就组织在主键 B+ 树的叶子里,主键越短、越单调递增,二级索引和插入都越省;用无意义的 UUID 做主键会把树挤得七零八落。
二是并发控制。 MyISAM 一条 UPDATE 锁整张表,读也会被写堵住,写写完全串行。InnoDB 走 MVCC:普通 SELECT 读一致性快照不加锁,只有 FOR UPDATE、LOCK IN SHARE MODE 和写操作才加行锁。读多写多的业务,这个差距是数量级的。
count(*) 那点差异
MyISAM 把行数存在表头,SELECT COUNT(*) FROM t 是 O(1)。InnoDB 因为 MVCC,不同事务在同一时刻看到的行数本来就可能不一样(有人有未提交的插入、有人在更老的快照上),没法只存一个"正确"的数字,只能扫。这是 InnoDB 的固有代价,不是实现得差。
两个容易忽略的行为差异
自增计数器。 5.7 的 InnoDB 不把自增值持久化,实例重启后按"当前最大主键值加一"重新初始化。也就是说,删掉尾部若干行再重启,被删掉的主键值可能被重新用上;MyISAM 把计数器存在数据文件头里,重启不会回退。依赖主键"绝对单调"做增量同步的脚本,这一点要提前考虑(自增值的持久化是 8.0 才改进的)。
空间回收。 MyISAM 删行会留下空洞,但文件不缩;InnoDB 大量删除后 .ibd 同样不会自动变小,需要 OPTIMIZE TABLE(实际是重建)才还给操作系统。两者都要靠磁盘余量来兜底,只是 MyISAM 还能用 myisamchk 离线修复,InnoDB 出问题一般靠 binlog 加备份重放。
选型建议
- 新项目一律 InnoDB:要事务、要崩溃恢复、要行锁,没有例外
- 纯只读的统计缓存表、临时导入的中转表、极端并发读且写入是整表覆盖重建的场景,MyISAM 可以考虑
- 全文检索优先想清楚要不要上专门的搜索引擎,而不是靠换引擎解决
- 遇到历史 MyISAM 表要改:
ALTER TABLE article ENGINE = InnoDB;
改之前先确认表上没有 FULLTEXT/AUTO_INCREMENT 组合列这些行为不同的定义,大表建议用 pt-online-schema-change 之类的工具在线做,ALTER 期间会锁表。
小结
- MyISAM 结构简单、读场景省内存,但没有事务、只有表锁、崩了容易坏
- InnoDB 用聚簇索引 + redo log + MVCC 换来一致性和并发,代价是 count 与空间占用
- 今天的选择基本只有一条:默认 InnoDB,除非能明确说出不需要它的哪一项