MySQL-MVCC原理

偏执的太偏执、 2022-10-06 15:54 208阅读 0赞

1. 事务隔离级别

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

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

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

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

2. 两种读的方式

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

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

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

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

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

都是 MVCC 搞的鬼。

3. MVCC

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

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

  1. 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 四个事务同时开启,并且执行下面的操作。









































1 2 3 4
Step1 事务开启 事务开启 事务开启 事务开启
Step2 select record1
Step3 update record1;commit;
Step4 select record1

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

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

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

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

此时的读视图:

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

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

  1. 可见性判断规则:
  2. 1. 首先比较 DB_TRX_ID < up_limit_id, 如果小于,则当前事务能看到 DB_TRX_ID 所在的记录,如果大于等于进入下一个判断;
  3. 2. 接下来判断 DB_TRX_ID >= low_limit_id, 如果大于等于则代表 DB_TRX_ID 所在的记录在 Read View 生成后才出现的,那么对于当前事务肯定是不可见的,如果小于,则进入下一步判断。
  4. 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,内容如下:

  1. list: 1,2,3,4 # 生成 read view 时活跃的事物id。
  2. up_limit_id1
  3. lower_limit_id5 # 尚未分配的下一个事务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修改后的数据。


























2 4
Step1 事务开启 事务开启
Step2 update record1;commit;
Step3 select record1

博客中写的是正确的…

image-20210614191615223

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

发表评论

表情:
评论列表 (有 0 条评论,208人围观)

还没有评论,来说两句吧...

相关阅读

    相关 jsonp原理(jsonp原理)

    JSON和JSONP的区别,以及使用方法 1、区别如下: (1)、定义不同 JSON是一种基于文本的数据交换方式(不支持跨域),而JSONP是一种非官方跨域数据交互协

    相关 mysqlmvcc-csdn

    既然MySQL中InnoDB使用MVCC,为什么REPEATABLE-READ不能消除幻读 第一个问题: 新版本的mysql通过mvcc解决了幻读的问题,所以你没有看到

    相关 mysqlmvcc解决幻读

    求数据库大神,mysql事务隔离级别repeatable-read 详解 术式之后皆为逻辑,一切皆为需求和实现。希望此文能从需求、现状和解决方式的角度帮大家理解隔离级别。