Appearance
08|InnoDB 存储引擎
前面建表时都写了 ENGINE=InnoDB。为什么用 InnoDB?它和别的存储引擎有什么区别?为什么 MySQL 断电重启后数据不丢、为什么能回滚事务、为什么有行锁——这些都和 InnoDB 的内部机制有关。
本篇讲 InnoDB 存储引擎的几个核心:Buffer Pool(内存缓存)、Redo Log(崩溃恢复)、Undo Log(回滚)、表空间。理解了这些,出问题时能判断该往哪个方向查。
一、Buffer Pool:MySQL 的工作台
InnoDB 读写数据不直接操作磁盘——太慢了。它先把数据从磁盘读到内存里的一块区域,在内存里改,改完再刷回磁盘。这块内存就是 Buffer Pool。
可以理解成工作台。磁盘像书架,书(数据)平时在书架上。要看一本书,先从书架拿到工作台上。工作台越大,能同时摊开的书越多。工作台太小,看几页就要放回书架、再拿新的,大部分时间花在"来回拿"而不是"看"上。
工作台够不够大
sql
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';两个指标:
Innodb_buffer_pool_read_requests— 总共要从 Buffer Pool 读多少次(逻辑读)Innodb_buffer_pool_reads— 其中多少次 Buffer Pool 没有,得去磁盘拿(物理读)
命中率:
text
(1 - Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests) × 100%99% 以上才算健康。低于 99%,说明工作台太小,大部分时间花在从磁盘搬数据上。
调多大
sql
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';机器只跑 MySQL 给到物理内存 70%-80%。机器还跑别的服务要留一些。8.0+ 支持在线调,不用重启:
sql
SET GLOBAL innodb_buffer_pool_size = 17179869184; -- 16G出问题时的表现
查询本身不慢(EXPLAIN 走索引了),但就是响应慢——大概率 Buffer Pool 太小,频繁去磁盘读。但注意:EXPLAIN 显示 type = ALL(全表扫),先解决索引问题,不是 Buffer Pool。先看索引,再看 Buffer Pool,顺序别反。
二、Redo Log:干完活的底单
Buffer Pool 在内存里改数据快,但有个问题:机器突然断电,内存里的数据全丢了怎么办?
InnoDB 靠 redo log 解决。改数据时,InnoDB 先把"改了哪几页数据"记到 redo log(只追加写的文件),再改内存里的 Buffer Pool。
理解成快递底单:寄包裹时快递员先让你填一张底单(日志),底单落袋了,包裹后面慢慢送。万一包裹丢了,凭底单可以重新发一份。
redo log 的作用是 MySQL 崩溃后,通过重放 redo log 把已提交但没写回磁盘的数据恢复回来。 这个过程叫 crash recovery,重启时自动做,不用人工干预。
一个关键参数
sql
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';默认 1,每次事务提交都把 redo log 刷到磁盘。最安全——但每次提交都要写磁盘,写入量大时对 IO 有压力。改成 2 表示每秒刷一次,OS 崩溃时可能丢 1 秒数据。核心业务库保持 1,日志类、可接受少量丢数据的库才考虑调低。
redo log 和 binlog 的区别
这两个经常搞混,职责完全不同:
| redo log | binlog | |
|---|---|---|
| 谁管的 | InnoDB 引擎层 | MySQL Server 层 |
| 记什么 | 哪个数据页被改了 | 哪条 SQL 或哪行变更被提交 |
| 干什么用 | 崩溃恢复 | 主从复制、时间点恢复 |
| 要不要人工管 | 不用,自动 | 要,备份和清理策略 |
崩溃恢复靠 redo,复制和恢复靠 binlog。 redo 是快递员的底单(保证包裹不丢),binlog 是你的寄件记录(可以查某天寄了什么)。
三、Undo Log:后悔药
开了个事务,先插了一条数据,又觉得不对想回滚。InnoDB 怎么知道插入之前是什么状态?靠 undo log。
undo log 记录的是修改前的老数据。插入前记下"这里原来是空的",更新前记下"原来是什么值"。回滚时按 undo log 把数据改回去。
undo log 还有一个重要用途——MVCC(多版本并发控制)。一个事务在改数据,另一个事务同时读这条数据,读到的应该是改之前的值(没提交的不能读)。undo log 存了老版本,让读操作能拿到一致性的快照,不用等写操作完成。这就是为什么 InnoDB 读写不互相阻塞(行锁不阻塞普通读)。
undo log 和长事务
undo log 要保留到事务结束(回滚或提交后才能清理)。如果一个事务开着一直不提交(长事务),它对应的 undo log 一直占着空间,表空间越来越大,性能下降。
sql
-- 看有没有跑很久的事务
SELECT * FROM information_schema.innodb_trx
ORDER BY trx_started ASC LIMIT 10;看到跑了几个小时的事务要排查——多半是应用开了连接没提交(忘了 commit)或连接池泄漏。
四、表空间
InnoDB 的数据存在表空间里。每张表的数据存哪:
sql
SHOW VARIABLES LIKE 'innodb_file_per_table';ON(8.0 默认):每张表单独一个 .ibd 文件,存在 datadir/库名/表名.ibd。管理和备份方便,删表直接删文件释放空间。
OFF:所有表共享一个系统表空间(ibdata1)。删表不释放空间,文件越来越大。不推荐。
8.0 默认每表一个文件,不用管这个参数。但要记着数据存在哪——排查磁盘满、要单独备份某张表时知道去哪找。
五、这几个机制怎么配合
一条 UPDATE 从执行到完成,InnoDB 内部走了一遍:
text
1. 从磁盘读数据页到 Buffer Pool(如果不在内存)
2. 记 undo log(记下改之前的值,用于回滚和 MVCC)
3. 改 Buffer Pool 里的数据页
4. 记 redo log(记下改了哪个页,用于崩溃恢复)
5. 记 binlog(Server 层记变更,用于复制和恢复)
6. 提交事务,redo log 刷盘(双 1 配置下)后面 Buffer Pool 里的脏页(改了但没刷盘的页)会在后台慢慢刷回磁盘表空间。断电了不怕——redo log 在磁盘上,重启时重放恢复。
理解这个流程,就能解释很多现象:为什么断电不丢已提交的数据(redo log)、为什么能回滚(undo log)、为什么长事务有问题(undo log 不清理)、为什么改数据要先读进内存(Buffer Pool)。
存储引擎之后
知道了 InnoDB 怎么存数据、怎么恢复、怎么回滚。但多个事务同时操作时怎么不互相干扰——事务和锁——下一篇讲。这是"为什么有锁等待、为什么会死锁"的根因。