MySQL-MVCC原理

1. 事务隔离级别

读未提交:事务A 可以读到事务 B 修改了但还未提交的记录。

读提交:事务A 只能读到 事务 B 修改了并且提交的记录。

可重复读:事务A 读不到 事务 B 修改了并且提交的记录。

串行化:没有并发都是串行执行。

2. 两种读的方式

当前读:读取的是最新的版本数据。

快照读:读取的是历史版本数据。

怎么判断是快照读还是当前读?

  • 如果执行的 SQL 语句是 insertupdatedeletefor updatelock in share model ,属于当前读。
  • 如果执行的 SQL 是纯粹的 select ,属于快照读。

同样是纯 select 读,有时读的是最新数据,有时读的是历史数据,这就是很困惑的地方。

都是 MVCC 搞的鬼。

3. MVCC

MVCC 全称 多版本并发控制。表中一行数据在更新时,将它的历史版本给保存下来了,存放在 undolog 中。

一行数据除了我们自定义的字段以外,还有三个隐藏字段。

field1, field2, field3, 隐藏Id,事务Id,回滚指针。 

隐藏Id:如果有主键,就是用主键,没有主键就使用唯一索引,如果连唯一索引都没有,就使用一个六字节的 rowId。

事务Id:记录的这个版本是被哪个事务修改的。(事务的版本每次都是递增的)

回滚指针:指向该记录的上一个历史版本。

问题来了,当一行记录有多个历史版本的时候,该读取哪一个历史版本。

这就涉及到了一个算法,可见性算法,用它来判断出当前事务读取记录的哪一个历史版本。

4. 读视图和可见性分析

读视图(Read View): 当进行快照读的时候会生成一个对象,该对象里保存了不同的事务信息,通过这些信息来做可见性判断。read view 对象中有三个字段:

list:保存生成 read view 时活跃的事物的 id。

up_limit_id:当前活跃事务id 的最小值。

lower_limit_id:尚未分配的下一个事务 id。

也就是说 B 事务修改并且提交的记录,A 事务能否看到,是要通过可见性分析来判断的。

假设一个场景:隔离级别是读提交,1、2、3、4 四个事务同时开启,并且执行下面的操作。

1234
Step1事务开启事务开启事务开启事务开启
Step2select record1
Step3update record1;commit;
Step4select record1

操作很简单,事务4 修改并提交了记录,事务2 在step4时刻又对该条记录执行快照读。

问题:事务2 在 step4 执行快照读的时候,能否读取到事务4 修改后的数据?

能不能读到,得先经过读视图做可见性判断。

因为只有 事务4 对数据进行了修改,所以 undolog 中只有一个历史版本。(这个“历史版本” 指的是事务4修改后的那条记录)

此时的读视图:

list: 1,2,3 	# 生成 read view 时活跃的事物id。
up_limit_id:1
lower_limit_id:5   # 尚未分配的下一个事务id。

接着要拿着 read view 中的值去可见性判断规则中做对比。

可见性判断规则:
1. 首先比较 DB_TRX_ID < up_limit_id, 如果小于,则当前事务能看到 DB_TRX_ID 所在的记录,如果大于等于进入下一个判断;
2. 接下来判断 DB_TRX_ID >= low_limit_id, 如果大于等于则代表 DB_TRX_ID 所在的记录在 Read View 生成后才出现的,那么对于当前事务肯定是不可见的,如果小于,则进入下一步判断。
3. 判断 DB_TRX_ID 是否在活跃事务中,如果在,则代表 Read View 生成时刻,这个事务还是活跃状态,还没有 commit,修改的数据,当前事务也是看不到。如果不在,则说明这个事务在 Read View 生成之前就已经开始 commit,那么修改的结果是能够看见的。

因为是事务 4 对记录进行了修改,所以 undolog 中“历史记录”的事务 id 是 4。即,可见性分析规则中的 DB_TRX_ID == 4。

开始判断:

  1. 4 < 1 ? 不成立,那进入下一个判断。
  2. 4 >=5 ? 不成立,那进入下一个判断。
  3. 4 在活跃事务中? 不在,那就是可见的。

结论: 事务2 能读取到 事务4 修改后的数据。这也符合读提交的隔离效果。

如果数据库的隔离级别是可重复读,我们先能确定的是:事务2 是不能够读取到事务4 修改并提交后的数据的。

可见性规则都是一样的,为什么在读提交和可重复读不同的隔离级别下,计算出的可见性不一样呢?

是哪里发生了变化?

答案是:read view 的生成时机不一样,所以计算出了不同的可见性。

读提交级别下,每次快照读时创建 read view。

可重复读级别下,当第一次执行 select 时,就生成可 read view,后面该事物中的所有快照读都使用这个read view。

可重复读隔离级别下走一遍流程:

事务2 在 step2 时就生成了 read view,内容如下:

list: 1,2,3,4 	# 生成 read view 时活跃的事物id。
up_limit_id:1
lower_limit_id:5   # 尚未分配的下一个事务id。

判断下事务2 在 step4 时,能否读到事务4 修改后的数据?

  1. 4 < 1 ? 不成立,那进入下一个判断。
  2. 4 >=5 ? 不成立,那进入下一个判断。
  3. 4 在活跃事务中? 在,那就是不可见的。

结论:事务2 不能读取到事务4 修改后的数据。事务2 在 step2 和 step4 两次读取的 record1 是一样的,符合可重复读的隔离效果。

5. 可重复读是怎么解决幻读问题的

对于快照读,readview 是在第一次 select 时生成的,以后的每次读都使用该 readview 做可见性分析,所以看不到其他事务对记录的插入和删除,从而解决的幻读问题。

对于当前读,可重复读隔离界别下会自动开启间隙锁,select * from tables where id >2 and id < 6; 这个 SQL 会对 2、6之间的索引加排它锁,当有其他事务在 2到6之间插入或者删除时,都会陷入阻塞,从而解决了幻读问题。

6. 一个小实验

在有些博客中写到:可重复读在第一次 select 的时候生成 read view,之后的 select 都使用该 read view。

如果真的是这样,在可重复读的隔离级别下,下面的操作中,事务2 应该能看到 事务4修改后的数据。

24
Step1事务开启事务开启
Step2update record1;commit;
Step3select record1

博客中写的是正确的…

image-20210614191615223

注:记忆有点模糊了,重新整理了一下这块的知识。文章的大部分内容来源于视频,更多更详细的内容可以自己看视频。先申明,里面有干货,也有广告。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值