上一篇顺着一条 SQL 走完了 Server 层到引擎层的链路,最后停在存储引擎层,说数据真正存在那里。这篇把这一层单独拿出来看:它到底管什么,MySQL 有哪些引擎,平时为什么用 InnoDB。
先澄清一个容易误解的点:存储引擎不是一个可选项,每张表都必须有一个 。建表不写 ENGINE,它就取默认值,MySQL 5.5 之后默认是 InnoDB。
存储引擎管什么
Server 层把 SQL 解析、优化成一份执行计划,但"数据存在磁盘的什么位置、怎么找、怎么改、加锁加到什么粒度"是存储引擎的事。
回忆一下执行器那一段:执行器调用的是一套引擎接口,有没有索引决定了它调"取第一行"还是"取满足条件的下一行",但它不知道数据到底是怎么找到的,那是引擎内部的实现。
| 事情 | 谁负责 |
|---|---|
| SQL 语法语义、权限、执行计划 | Server 层 |
| 数据怎么组织、怎么读写、怎么加锁、支不支持事务 | 存储引擎层 |
这个分工带来的直接结果是引擎可插拔:换引擎不用改 SQL,换的是底层行为。所以"用什么引擎"这个问题,本质是在问"这张表要不要事务、要不要行锁、数据怎么组织"。
有哪些引擎
SHOW ENGINES 列出当前实例支持哪些,Support 是 DEFAULT 的那一行就是默认引擎:
text
Engine Support Transactions
InnoDB DEFAULT YES
MyISAM YES NO
MEMORY YES NO
ARCHIVE YES NO
CSV YES NO
BLACKHOLE YES NO
MRG_MYISAM YES NO
PERFORMANCE_SCHEMA YES NO
建表时用 ENGINE 指定,不写就是 InnoDB:
sql
CREATE TABLE t (
id int primary key,
name varchar(20)
) ENGINE = MyISAM;
一个库里不同的表可以用不同的引擎,引擎的粒度是表,不是库。
真正需要认识的只有三个:InnoDB、MyISAM、Memory,剩下的 Archive、CSV、Blackhole 都是特定用途的边角引擎,日常基本用不到。
InnoDB
InnoDB 是现在的默认引擎,也是绝大多数场景唯一该选的。它和 MyISAM 的差别不是性能高低,而是支持的语义完全不同。
事务、行锁、外键
这三样是 MyISAM 给不了的:
- 事务 :InnoDB 支持
BEGIN/COMMIT/ROLLBACK,一组操作要么全成要么全不成,靠的是 undo log 和 redo log - 行级锁:只锁被改的那几行,别的事务改别的行不受影响,并发高
- 外键:真正能强制外键约束的只有 InnoDB
关于行锁有一个常被忽略的点:行锁是加在索引上的 。如果 UPDATE 的条件没走索引,InnoDB 定位不到具体是哪些行,就只能把整张表锁住,行为退化成表锁。所以"没走索引"不只是查询慢,写操作还会把并发一起拖垮。
崩溃恢复
MyISAM 是没有崩溃恢复能力的。InnoDB 有两套日志兜底:进程或者机器挂掉,重启的时候先按 redo log 把已提交但没落盘的改动重做,再按 undo log 把没提交的事务回滚掉,数据自动回到一致状态。
这套东西的细节在 Redo Log 那篇里,这里只要记住:InnoDB 掉电不会把表搞坏,MyISAM 会。
数据就存在主键索引里
InnoDB 的表,数据行本身存在主键索引的叶子节点上,这种结构叫聚簇索引(clustered index)。也就是说主键索引的叶子存的就是完整的一行,找到主键就找到了整行数据。
这带来两个后果:
- 主键即位置。按主键查最快,一次索引查找就拿到整行;按普通索引查,叶子节点里存的是主键值,得再回主键索引查一次,这就是回表
- 主键最好用自增的。主键决定了行在页里的物理顺序,如果主键是随机的(比如 UUID),新插入的行可能落进任意一个已满的页,触发页分裂和数据搬移;自增主键则总是往最后一页追加
顺带解释一个常见问题:为什么 InnoDB 的
count(*)很慢,MyISAM 却很快?MyISAM 直接在文件头存了行数,返回的是常数。InnoDB 不行,因为同一行在不同事务眼里存不存在都不一样(MVCC 的多版本),没法维护一个全局精确的行数,只能一行行数。
MyISAM
MyISAM 是早年的默认引擎,现在除了个别场景已经不用了,但面试里还常和 InnoDB 摆在一起对比。
表锁,不支持事务
MyISAM 的锁粒度是表级:写操作会把整张表锁住,别的连接读写这张表都得等。加上不支持事务,一组操作中间挂了,前面已经改掉的不会回滚。
所以 MyISAM 只适合读多写少、且对一致性要求不高的场景。
索引和数据分开存
MyISAM 的数据和索引是两个独立的文件,一张表对应三个:
| 文件 | 内容 |
|---|---|
.frm |
表结构 |
.MYD |
数据 |
.MYI |
索引 |
它的索引是非聚簇 的:B+ 树的叶子节点存的不是整行,而是数据行的物理地址,拿到地址再去 .MYD 里定位。正好和 InnoDB 相反,InnoDB 是索引即数据。
count(*) 快,但会坏
前面说过,MyISAM 的文件头存了总行数,count(*) 不带 where 时直接返回,不用扫表,这一点上确实比 InnoDB 快。
反过来它没有崩溃恢复日志,服务器突然断电,表可能处于损坏状态,重启后要用 REPAIR TABLE 尝试修复,修不好的部分数据就丢了。InnoDB 不会出现这个问题。
Memory 和其它
MEMORY 把数据全放在内存里,用哈希索引,重启之后数据全丢,只剩表结构。适合做临时表,或者放一些能随时重建的小尺寸缓存。因为表锁加上哈希索引只擅长等值查找,拿它当主力存储不合适。
其余几个基本用不到,知道大概就行:
| 引擎 | 特点 |
|---|---|
ARCHIVE |
只支持插入和查询,压缩存储,适合归档日志 |
CSV |
数据以 CSV 文本存,能被外部程序直接读,不支持索引和事务 |
BLACKHOLE |
写进去就丢,读永远为空,只用来验证 binlog 之类的链路 |
InnoDB 和 MyISAM 对比
| 维度 | InnoDB | MyISAM |
|---|---|---|
| 事务 | 支持 | 不支持 |
| 锁粒度 | 行级锁 | 表级锁 |
| 外键 | 支持 | 不支持 |
| 崩溃恢复 | 有 redo/undo,自动恢复 | 无,可能损坏需修复 |
| 索引结构 | 聚簇索引,索引即数据 | 非聚簇,索引存行地址 |
count(*) |
要扫表,慢 | 存了行数,快 |
| 全文索引 | 5.6 之后支持 | 早就支持 |
| 适用场景 | 绝大多数场景 | 读多写少、无事务要求的旧系统 |
怎么选
默认选 InnoDB,这句话适用于绝大多数情况。需要事务、需要并发写、需要崩溃后能自动恢复,这些都是现代业务的底线要求,MyISAM 给不了。
改成其它引擎的理由只有两类:数据明确不需要持久化(MEMORY),或者只需要顺序写入归档(ARCHIVE)。不要为了"读快"去选 MyISAM,它那点读性能优势在 InnoDB 的 buffer pool 面前基本不存在,却要拿掉事务和崩溃恢复,这笔账不划算。