MVCC
相信大家多多少少都听过或者了解过MVCC,知道它大致是一个什么东西,但是想把它讲明白,讲清楚却不是那么容易。
首先我们先解析一下MVCC 的中文名称------多版本控制并发。这个词语我们可以拆分为两个部分来解读,一个是多版本,一个是控制并发;
首先多版本的意思就是,在数据库的每一行数据,它都可能存在多个版本;然后控制并发就是要从这多个版本中,选出某个适合当前操作的版本。
当前读和快照读
所谓的当前读就是在我们使用
select ... lock in share mode,select ... for update和insert、update、delete
等操作的时候读取的是数据的最新版本,当前读会对读取到的记录加锁。当前读是和锁息息相关的
快照读就是想要读取的行 正在执行 DELETE 或 UPDATE
操作,这时读取操作不会去等待行上锁的释放。相反地,会去读取行的一个快照数据(从多个版本中获取某一个版本的数据),这里的读取其实就是基于MVCC 的非阻塞读。
MVCC 的实现的核心
MVCC 的实现依赖于:隐藏字段、Read View、undo log。
隐藏字段
在内部,InnoDB 存储引擎为每行数据添加了两个 隐藏字段(其实是四个字段,但是这里MVCC机制一般只需要用到两个字段):
● DB_TRX_ID
(事务id):表示最后一次插入或更新该行的事务 id。
● DB_ROLL_PTR
(回滚id) 回滚指针,指向该行的 undo log 。如果该行未被更新,则为空
undo-log
undo log 主要有两个作用:
● 当事务回滚时用于将数据恢复到修改前的样子
● 另一个作用是 MVCC ,当读取记录时,若该记录被其他事务占用或当前版本对该事务不可见,则可以通过 undo log 读取之前的版本数据,以此实现快照读
要注意:Undo-log中并不仅仅只存储一条旧版本数据,其实在该日志中会有一个版本链,啥意思呢?举个例子:
sql
SELECT * FROM `zz_users` WHERE user_id = 1;
+---------+-----------+----------+----------+---------------------+
| user_id | user_name | user_sex | password | register_time |
+---------+-----------+----------+----------+---------------------+
| 1 | 熊猫 | 女 | 6666 | 2022-08-14 15:22:01 |
+---------+-----------+----------+----------+---------------------+
UPDATE `zz_users` SET user_name = "竹子" WHERE user_id = 1;
UPDATE `zz_users` SET user_sex = "男" WHERE user_id = 1;
比如上述这段SQL隶属于trx_id=1
的T1事务,其中对同一条数据改动了两次,那Undo-log日志中只会存储一条旧版本数据吗? 不会!
从上图中可明显看出:同一行的不同版本数据,会以roll_ptr
回滚指针作为链接点,然后将所有的旧版本数据组成一个单向链表。最新的旧版本数据,都会插入到链表头中.
ReadView(读视图)
通过上面的隐藏字段和undo log ,我们已经清楚了同一行数据的多个不同版本是怎么实现的。
但是对于这多个版本,我究竟在不同的情况下要读取哪个版本呢? 这就是我们的read view 来实现的。
Read View主要是用来做可见性判断,里面保存了 "当前对本事务不可见的其他活跃事务"
通过这个读视图的属性,加上特定规则的算法,我们就可以实现一个事务在什么情况下可以看到哪个版本的数据
具体的可见性算法(也称之为版本链的访问规则)如下:
小结
在了解了MVCC 的三大核心之后,我们可以简单的概括出其实现多版本控制并发的机制。InnoDB 通过数据行的 DB_TRX_ID
和 Read View 来判断数据的可见性(可见性算法),如不可见,则通过数据行的 DB_ROLL_PTR
找到 undo log 中的历史版本。实现每个事务读到的数据版本可能是不一样的。
MVCC 在RR 和RC 下不同的表现
在同一个事务 T1 下:
RC 可能返回两次快照读是不同的版本数据(毕竟是读已提交嘛)
而RR 则是返回两次快照读都是一样版本数据
这里其实就是这两种隔离级别生成的readview 的策略不同,RC 的策略是会去读取新的readview ,而RR 得策略是一直使用第一次读得的readview 。
MVCC➕Next-key-Lock 防止幻读
InnoDB存储引擎在 RR 级别下通过 MVCC和 Next-key Lock 来解决幻读问题:
1、执行普通 select,此时会以 MVCC 快照读的方式读取数据
在快照读的情况下,RR 隔离级别只会在事务开启后的第一次查询生成 Read View ,并使用至事务提交。所以在生成 Read View 之后其它事务所做的更新、插入记录版本对当前事务并不可见,实现了可重复读和防止快照读下的 "幻读"
2、执行 select...for update/lock in share mode、insert、update、delete
等当前读
在当前读下,读取的都是最新的数据,如果其它事务有插入新的记录,并且刚好在当前事务查询范围内,就会产生幻读!InnoDB 使用 临键锁 来防止这种情况。当执行当前读时,会锁定读取到的记录的同时,锁定它们的间隙,防止其它事务在查询范围内插入数据。只要我不让你插入,就不会发生幻读
🔥参考资料: