本文针对InnoDB进行深入理解(学习向)
一、架构:
下面图祖传的了,我截自【MySQL | 进阶篇】08、InnoDB 引擎架构、事务原理及 MVCC 讲解_事务、索引、mvcc机制-优快云博客
解释一下:
由2部分组成
1、内存
Buffer Pool(缓冲池): 缓存表数据和索引,减少磁盘 I/O。
组成:数据页+索引页+change buffer二级索引缓存+ Adaptive Hash Index 自适应哈希索引
change buffer是针对二级索引的更改(有更改先写到buffer中,)
2、磁盘
- 系统表空间
- 独立表空间
- 通用表空间
- 临时表空间
架构看起来很复杂,可以先往下看,你现在只需要知道:
InnoDB一端受数据库服务层控制,先操作Buffer Pool缓冲池,再用缓冲池与磁盘做交互
Buffer Pool缓冲池是个桥梁,非常关键!
二、工作原理:
2.1 读数据
流程是:
1. 先查找Adaptive Hash来判断要查询的页是否在Buffer Pool(缓存加载是按页为单位,而不是行)
2. 如果没命中,从磁盘加载该页到Buffer Pool。更新LRU链表(记录页的访问热度,优先保留高频访问的页。低频的会在空间不足时先淘汰)
2.2 写数据
流程稍复杂一点:
1. 加锁(先不展开讲)
2. 写undo log
写入内容是:
update
:记录修改前的完整行数据。delete
:记录被删除的行数据。insert
:记录新行的主键(用于回滚时删除)。
undo log就是一个备份作用,为了后续的回滚
至于undo log文件位置,数据库Data文件夹下的undo_001和undo_002(默认2个,可以改)
undo log是所有库公用的文件,因为事务可以跨库,所以undo log也需要跨库
3. 写入buffer pool,写后的页标为“脏页”
若修改的是 非唯一索引,先写入 Change Buffer(减少随机 I/O)
4. 写redo log
写入内容类似于:“页号X,偏移量Y处写入数据Z”
与undo log的位置一样
4.5 异步刷盘:
刷盘就是把redo log的数据写入磁盘,有两种刷盘策略
innodb_flush_log_at_trx_commit
=0 每秒定时刷盘 性能最高,最不安全!
innodb_flush_log_at_trx_commit
=1 事务提交即刷盘 性能最低,最安全
innodb_flush_log_at_trx_commit
=2 事务提交写入page cache
page cache你可能不熟悉, 下图来自JavaGuideMySQL三大日志(binlog、redo log和undo log)详解 | JavaGuide 描述page cache,InnoDB有额外线程定时用page cache刷盘,如果innodb_flush_log_at_trx_commit
=2,那么page cache刷盘频率会更高
5. 事务提交:
两阶段提交:
Binlog是服务层的日志,事务提交时一并写入,与引擎无关
Redolog是引擎层日志,事务执行时持续写入,仅InnoDB有
两阶段提交是为了保证 Binlog、Redolog和数据库内容三者一致
Binlog的作用是:保证了 MySQL 集群架构的数据一致性。
redolog的作用是:让 InnoDB 存储引擎拥有了崩溃恢复能力。
二者配合起来才能保证数据库的高可用
三、脏读、不可重复读、幻读:
很多人知道脏读、不可重复读、幻读,但是不很理解底层是怎么样的情况,下边结合InnoDB的架构来分析下:
3.1 脏读
常规解释:
事务A在写数据,事务B读取了事务A还没提交的数据,若事务A最终回滚,事务B读到的就是无效的“脏数据”。
底层:
Buffer Pool 的脏页
- 事务A修改数据时,先在 Buffer Pool 中生成脏页(内存中修改但未刷盘)。
- 事务B直接从 Buffer Pool 读取该脏页(读未提交隔离级别下不检查事务状态)。
Undo Log 未生效
- 事务A的修改尚未提交,对应的 Undo Log 仍处于活跃状态(未释放)。
- 事务B直接读Buffer Pool数据。(读未提交(Read Uncommitted)隔离级别会忽略 MVCC 版本链)
A回滚,B读到的数据失效
若事务A回滚,会通过 Undo Log 恢复数据,导致事务B之前读到的数据失效。
3.2 不可重复读
常规解释:
事务A内多次读取同一数据,期间事务B修改并提交了该数据,导致事务A前后读取结果不一致。
底层:
MVCC 的 ReadView 生成时机
- 在读已提交(Read Committed)隔离级别下,每次查询都会生成新的 ReadView。
- 事务B提交后,事务A的新 ReadView 会看到事务B的修改(即使事务A未结束)。
Undo Log 版本链的可见性
- 事务A首次读取时,从 Undo Log 链中找到可见版本(假设为版本V1)。
- 事务B提交后,Undo Log 链新增版本V2(已提交),事务A的新查询会跳过V1读取V2。
3.3 幻读
常规解释:
事务A内多次执行相同查询,期间事务B插入或删除了符合查询条件的行,导致事务A两次查询的数据行数不一致
底层:
MVCC 的快照读 vs 当前读
- 快照读(普通SELECT):基于首次 ReadView,看不到其他事务插入的数据。
- 当前读(SELECT FOR UPDATE):会看到最新提交的数据,导致幻读。
间隙锁(Gap Lock)的缺失
- 在可重复读(Repeatable Read)隔离级别下:
- 若事务A未显式加锁(如
FOR UPDATE
),事务B可插入符合条件的新行。- 事务A的当前读会看到这些新行(幻读)。
临键锁(Next-Key Lock)的解决方案
- InnoDB 默认在 RR 隔离级别下对范围查询加临键锁(记录锁 + 间隙锁),阻止其他事务插入。