MySQL MVCC
1、简介
MVCC(Multi-Version Concurrency Control,多版本并发控制),是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问。用于支持 读已提交(RC) 和 可重复读(RR) 隔离级别的实现。
MVCC的实现依赖于六个概念:【隐式字段】【undo日志】【版本链】【快照读和当前读】【读视图】。
2、InnoDB 表的隐藏字段
在 MySQL 中,InnoDB 会为每行记录后面添加两个 or 三个隐藏字段:
DB_ROW_ID
(可能有):行ID,MySQL 的 B+Tree 索引特性要求每个表必须要有一个主键。如果没有设置的话,会自动寻找第一个不包含 NULL 的唯一索引列作为主键。如果还是找不到,就会在这个DB_ROW_ID
上自动生成一个唯一值,以此来当作主键(该列和MVCC的关系不大)。DB_TRX_ID
:事务ID,占用 6byte 的标识,记录的是当前事务在做 INSERT 或 UPDATE 语句操作时的 事务ID(DELETE语句被当做是UPDATE语句的特殊情况)。DB_ROLL_PTR
:回滚指针,占用 7byte,指向这条记录的上一个版本的 undo log 记录,通过它可以将不同的版本串联起来,形成版本链。相当于链表的 next指针。
注意,添加的隐藏字段并不是很多人认为的创建时间和删除时间,同时在 MySQL 中 MVCC 的实现也不是通过什么快照来实现的。之所以有这种说法可能是源自于《高性能MySQL》一书中对 MySQL 中 MVCC 的错误结论,然后就人云亦云传开了(注意,这里一直强调的是 MySQL 中 MVCC 的实现,是因为在不同的数据库中可能会有不同的实现)。所以说看源码和看官方文档才是最权威的解释。
#、3 undo log
undo log 一种用于撤销回退的日志。
undo log记录的是什么? undo log 中记录的是当前事务操作中的相反操作。如:
- 一条 insert 语句在 undo log 中会对应一条 delete 语句。
- 一条 delete 语句在 undo log 中会对应一条 insert 语句。
- 一条 update 语句在 undo log 中对应相反的 update 语句。
Undo log的工作原理
- 执行 update 操作时,事务A 提交时候(事务还没提交),会将数据进行备份,备份到对应的 undo buffer,当事务回滚时或者数据库崩溃时用于回滚事务。
undo log 的主要作用是事务回滚和实现 MVCC 快照读。
undo log 分为两种:
- insert undo log:事务在 insert 新记录时产生的 undo log。仅用于事务回滚,并且在事务提交后可以被立即丢弃。
- update undo log:事务在 update 或 delete 时产生的 undo log。不仅在事务回滚时需要,在实现 MVCC 快照读时也需要,所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被清理线程统一清除。
MVCC 实际上是使用的 update undo log 实现的快照读。
InnoDB 并不会真正地去开辟空间存储多个版本的行记录,只是借助 undo log 记录每次写操作的反向操作。所以 B+Tree 上对应的记录行只会有一个最新的版本,但是 InnoDB 可以根据 undo log 得到数据的历史版本,从而实现多版本控制。
4、版本链
当事务对某一行数据进行改动时,会产生一条 undo log,多个事务同时操作一条记录时,就会产生多个版本的 undo log,这些日志通过回滚指针(DB_ROLL_PTR
)连成一个链表,称为版本链。
假设现在有一张 account 表,其中有 id 和 name 两个字段,那么版本链的示意图如下:
5、快照读和当前读
快照读(Consistent Read,也叫普通读)
快照读读取的是记录数据的可见版本,不加锁,不加锁的普通 select 语句都是快照读,即不加锁的非阻塞读。
快照读的执行方式是生成 ReadView,直接利用 MVCC 机制来进行读取,并不会对记录进行加锁。
如下语句:
1 |
|
当前读(Locking Read,也称锁定读)
当前读 读取的是记录数据的最新版本,并且需要先获取对应记录的锁。如下语句:
1 |
|
6、读视图(Read View)
Read View 提供了某一时刻事务系统的快照,主要是用来做可见性判断, 里面保存了【对本事务不可见的其他活跃事务】。
开启事务后,会产生一个 Read View,实际是在执行 select 语句前才生成当前事务的 Read View,用来判断当前事务可见哪个版本的数据,即可见性判断。
Read View 的四个属性
MySQL5.7 源码中对 Read View 定义了四个属性,如下:
creator_trx_id
: 创建当前 Read View 的事务ID。m_ids
: 当前系统中所有的活跃事务(当前系统中开启了事务,但还没有提交的事务)的 id。m_low_limit_id
: 当前系统中活跃的读写事务中最小的事务id,即 m_ids 中的最小值。m_up_limit_id
: 当前系统中事务的 id 值最大的那个事务 id 值再加 1,也就是系统中下一个要生成的事务 id。
Read View 会根据这 4 个属性,结合 undo log 版本链中的属性,来实现 MVCC 机制,从而决定一个事务最后能读取到数据的哪个版本。
假设现在有 事务A 和 事务B 并发执行,事务A 的事务 id 为 10,事务B 的事务 id 为 20。
事务A 的 Read View:
m_ids=[10,20],m_low_limit_id=10,m_up_limit_id=21,creator_trx_id=10
事务B 的 Read View:
m_ids=[10,20],m_low_limit_id=10,m_up_limit_id=21,creator_trx_id=20
当执行 SELECT 语句的时候会创建 Read View,但是在读取已提交和可重复读两个事务级别下,生成 Read View 的策略是不一样的:
- 读取已提交级别是每执行一次 SELECT 语句就会重新生成一份 Read View。
- 而可重复读级别是只会在第一次 SELECT 语句执行的时候会生成一份,后续的 SELECT 语句会沿用之前生成的 Read View(即使后面有更新语句的话,也会继续沿用)。
Read View 可见性判断规则
当一个事务读取某条数据时,会通过每一条记录的隐藏字段 DB_TRX_ID
在坐标轴上的位置来进行可见性规则判断,如下:
DB_TRX_ID < m_low_limit_id
: 表示DB_TRX_ID
对应这条数据 在当前事务开启creator_trx_id
之前,其他的事务就已经将该条数据修改了并提交了事务(事务的 id 值是递增的),所以当前事务能读取到。DB_TRX_ID >= m_up_limit_id
: 表示在当前事务creator_trx_id
开启以后,有新的事务开启,并且新的事务修改了这行数据的值并提交了事务,因为这是creator_trx_id
后面的事务修改提交的数据,所以当前事务creator_trx_id
是不能读取到的。m_low_limit_id =< DB_TRX_ID < m_up_limit_id
:DB_TRX_ID 在 m_ids 数组中
: 表示DB_TRX_ID
和当前事务creator_trx_id
是在同一时刻开启的事务。DB_TRX_ID 不等于 creator_trx_id
:DB_TRX_ID
事务修改了数据的值,并提交了事务,而这个事务不是自己,所以当前事务creator_trx_id
不能读取到。DB_TRX_ID 等于 creator_trx_id
: 表明数据是自己修改并提交的,因此是可见的。
DB_TRX_ID 不在 m_ids 数组中
: 表示的是在当前事务creator_trx_id
开启之前,其他事务DB_TRX_ID
将数据修改后就已经提交了事务,所以当前事务能读取到。
7、MVCC实现原理
通过上述对 Read View 的分析可以总结出:InnoDB 实现 MVCC 是通过 Read View 与 Undo Log 实现的,Undo Log 保存了历史快照,形成版版本链,Read View 可见性规则判断当前版本的数据是否可见。
InnnoDB 执行查询语句的具体步骤为:
执行语句之前获取查询事务自己的 事务ID,即事务版本号。
通过 事务ID 获取 Read View。
查询存储的数据,将其
DB_TRX_ID
与 Read View 中的事务版本号creator_trx_id
进行比较。不符合 Read View 的可见性规则,则读取 Undo log 中历史快照数据。
找到当前事务能够读取的数据返回。
而在实际的使用过程中,Read View 在不同的隔离级别下是得工作方式是不一样。
读已提交(Read committed, RC)MVCC 实现原理:在读已提交的隔离级别下实现 MVCC,同一个事务里面,每一次查询都会产生一个新的 Read View 副本,这样可能造成同一个事务里前后读取数据可能不一致的问题(不可重复读并发问题)。
可重复读(Repeatable read,RR)MVCC 实现原理:在可重复读的隔离级别下实现 MVCC,同一个事务里面,多次查询,都只会产生一个共用 Read View,都是使用的执行第一次 select 语句时生成的 Read View,以此不可重复读并发问题。