Mysql专题|如果早知道MVCC可以这样学,我也不至于被面试官虐的这么惨!

2022-09-19 12:01:28 浏览数 (2)

什么是MVCC?

MVCC是Multi-Version Concurrency Control(多版本并发控制)的缩写。

MVCC解决了什么问题?

图片来自网络

我们知道在mysql中有四种事务隔离级别:读未提交、读已提交、可重复读和串行读。在四种隔离级别中,可重复读就是通过MVCC实现的。通过MVCC,能够保证在事务开启后,保证每次读取的数据都是一样的;但是却不能解决幻读的问题,庆幸的是mysql使用间隙锁解决了在可重复读级别下出现的幻读问题。

MVCC实现原理

MVCC主要是借助mysql的undo log和一致性视图(快照)来实现。

undo log 记录了数据在变迁过程中所关联的事务ID;

一致性视图(快照)保存了线程在开启一个事务之后,数据的一个快照点,记录当前事务的状态。

那么MVCC是如何通过undolog 和一致性视图来实现可重复读的呢?

首先我们思考这样一个问题,在可重复读模式下,开启一个事务之后会是什么样的场景:

  • 能看到本事务开启前的所有已经提交的事务产生的数据
  • 不能看到未提交的事务产生的数据

假设每个事务都有自己的事务ID,并且这个id是递增的,后创建的事务ID大于先创建的事务ID

所以如果想要实现这样一个场景,开启事务后,需要保存以下两个数据状态:

  • 未提交的事务作为一个数组 un_commit[],按顺序排列
  • 生成一个下一个即将分配的事务ID MAX_ID

准备工作做好以后,我们先介绍下mysql在新增、删除和修改数据的时候,mysql底层是如何存储的

mysql 如何记录我们增删改的数据?

mysql在底层为undolog 中每条数据都会增加三个伪字段字段:创建事务ID,是否删除标记(默认否),上一版本指针

数据记录是按照数据更新时间从上往下排的,这里为了书写方便,更换了排列顺序,请注意区分

  • 初始结构

Id

Name

txc_id

是否删除

上一版本指针

1

yang

100

False

  • 修改 name= zhang

Id

Name

txc_id

是否删除

上一版本指针

1

yang

100

False

1

zhang

200

False

地址1

  • 新增id=2

Id

Name

txc_id

是否删除

上一版本指针

1

yang

100

False

1

zhang

200

False

地址1

2

lisi

300

False

  • 删除id=2

Id

Name

txc_id

是否删除

上一版本指针

1

yang

100

False

1

zhang

200

False

地址1

2

lisi

300

False

2

lisi

400

true

地址2

不管新增删除还是修改,都是复制一份数据,而不是在原有数据上操作,这样最终就会形成一个数据链,很适合做快照。

通过上面的描述,大家应该对mysql如何通过undolog存储我们的数据链有了一个大概的认识,现在我们回归正题:MVCC是如何通过undolog 来查找我们的数据,实现可重复读呢?

MVCC是如何查询我们想要的数据,保证可重复读呢?

在前面已经提到过,mysql在开启事务后,会生成一个一致性视图,其实对于程序来说就是记录当前的数据点:

  • 未提交的事务做一个数组 un_commit[],按顺序排列
  • 生成一个下次即将分配的事务ID MAX_ID

ok,现在我们利用这两组数据,来查找id为1的数据

假设当前分配的事务ID为300,目前有两个未提交的事务[100,200],我们现在模拟下查找流程

初始状态

Id

Name

txc_id

是否删除

上一版本指针

1

yang

50

False

A开启事务后,第一次查找

执行了第一条select语句时,系统分配了一个事务ID 300,此时有两个未提交的事务100,200,目前是想要查找id为1的记录

  • 比较第一条,提取创建事务id=50,比较后发现创建事务id小于当前事务ID=300,进入下一步
  • 判断 创建事务id小于最小的未提交事务id=100,则可以认为当前这条数据是在本事务开启之前就已经提交了,所以返回此条数据。
  • 查找完成
此时事务ID=100的修改了id=1 的数据,并且提交了事务

此时的数据长这样:

Id

Name

txc_id

是否删除

上一版本指针

1

zhang

100

False

地址1

1

yang

50

False

A事务此时进行第二次查找

从上往下找

  • 提取第一条数据,判断发现创建事务id=100是小于当前事务ID=300,则进入下一个判断
  • 判断发现事务id=100 是在 未提交数组[100,200]中,所以对当前事务事务是不可见的,进入下一个判断
  • 提取上一个版本指针的地址,定位到数据
  • 比较发现当前数据创建的事务id是50,小于最小的未提交事务的id,所以返回此条数据
此时事务ID=200的删除了id=1 的数据,没有提交事务

此时数据长这样:

Id

Name

txc_id

是否删除

上一版本指针

1

zhang

200

true

地址2

1

zhang

100

False

地址1

1

yang

50

False

A事务此时进行第二次查找

此次查找过程和上面一样,最终定位到事务id=50时产生的数据记录

A事务进行了update操作后,会更新数据视图

未提交数组:[200],当前预分配的事务ID=400

A开启事务后,进行第一次查询

生成数据视图保存点:未提交数组:[200],当前预分配的事务ID=400

  • 从第一条开始比较,发现事务Id=200在 未提交事务的数组中,则根据地址2找到下面一条记录
  • 创建事务id=100 小于最小的未提交事务id=200,则返回此条数据。

注意:在所有查找过程中,匹配到最终可见的数据后,还需要判断数据的删除标记为是否已经标记为删除状态,如果标记为删除状态,则不返回此条数据,并且终止向下查询!!!

福利大放送

关注微信公众号“AI码师”,领取2021最新面试资料和最新全套微服务教程

0 人点赞