mysql事务 -- MVCC多版本控制介绍(模拟过程),版本链(清理机制,不同操作对应的版本链),可见性判断(是否应该看见,源码,比较过程),RC和RR的本质区别(如何实现)

目录

MVCC多版本控制

介绍

模拟

版本链 

快照

相反sql的记录

清理机制

不同操作对应的版本链

删除操作

插入操作

查询操作

可见性判断

引入

我能看见的

不应该看见的

图示 

源码

过程

场景

比较步骤

RC和RR的本质区别

引入

实验

总结

介绍

总结


前提知识 -- 

mysql事务 -- 事务的隔离性(测试实验+介绍,脏读,不可重复读,可重复度读,幻读)-CSDN博客mysql事务 -- 事务id介绍,数据库表的隐藏列,undo log的简单介绍,当前读(select的当前读方式),快照读(与隔离性的关系),读视图(三个字段介绍)-CSDN博客

MVCC多版本控制

介绍

多版本并发控制( MVCC )是一种用来解决 读-写冲突 的无锁并发控制

  • 为事务分配单向增长的事务ID
  • 为每个修改保存一个版本,版本与事务ID关联
  • 读操作只读该事务开始前的数据库的快照

所以 MVCC 可以为数据库解决以下问题:

  • 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
  • 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题

 

模拟

假设,有一个事务10,要对某条记录做修改

  • 修改前,需要对记录加锁保护,并且拷贝一份放入undo log中
  • 类似写时拷贝

然后将备份数据的地址填入该记录中的DB_ROLL_PTR中,形成版本链

  • 然后再对原记录做修改

最后,更新DB_TRX_ID中的值为当前事务id(也就是10)

事务执行完成后,释放锁资源

版本链 

InnoDB 为每一行数据维护的一种数据结构,它记录了数据行的不同版本

  • 每当对一行数据进行插入、更新或删除操作时,都会创建一个新的版本,并将其链接到该行的版本链中

现在,又来了一个事务11,也是对这条记录做修改

  • 要修改的记录肯定是新数据,历史数据已经是固定的了

和上面的流程一样,加锁->拷贝->更新两个隐藏列数据(回滚指针,事务id)

  • 所以,我们继续扩充这个基于链表记录的历史版本链

这些一个一个的版本,称之为一个一个的快照

快照

快照是数据库在某一特定时刻的数据状态的副本

这就是为什么mysql可以实现事务隔离

  • 因为保存了多个版本的数据

相反sql的记录

除此之外,它还会在日志中记录当前操作的相反sql,以便回滚

  • 比如,执行一条insert,就存放一条对应的delete
  • 这样执行回滚操作时,逆向执行新增的sql,数据就恢复了

这些多版本数据,由mysql来维护,称之为MVCC

清理机制

这些版本链会一直持续下去吗,会不会把undo log塞满呢?

undo log是临时缓冲区,保存的是事务运行期间数据的历史版本

  • 事务一旦提交,该事务对应的版本链就会被释放掉
  • 当然,如果同时还有其他事务正在使用这些版本,就依然保留着
  • 总之,没有客户端使用某条记录时,再清理

InnoDB 使用后台线程定期检查和清理不再需要的 Undo Log 记录

不同操作对应的版本链

删除操作

删除操作对应的也有版本链

在删除一行记录时,数据库实际上会创建一个新的版本

  • 新版本将逻辑上被标记为“已删除”
  • 旧版本仍然可用于其他事务的读取操作,确保并发事务的隔离性

插入操作

insert也有吗?

  • 插入的是新数据,没有历史版本的数据与它对应

但是,插入的数据,也还是需要放入undo log中

  • 用来在不同隔离级别下让其他客户端看见
  • 以及还要将相反sql保存起来,用于后续的回滚操作

如果当前事务提交了,insert对应的历史记录就可以被删除

  • 因为这是新插入的数据,在隔离性的作用下,只有执行插入操作的事务会看到

查询操作

它不会对数据做修改

  • 所以维护多版本没有意义

那每个查询操作读取的是新数据还是历史版本呢?

  • 取决于隔离级别的设置,以及使用select的方式

可见性判断

引入

目前,我们就有了两个结构

  • 读视图(关于当前事务到来时,事务id的分配情况) -- 注意,读视图是在执行快照读后创建的,而不是事务到来就创建
  • 历史版本链(包括每个修改该条记录的事务id)

接下来,就是根据[当前事务id]和[版本链中的事务id]做对比,得到自己应该读取的历史版本

  • 这就是在进行可见性判断

我能看见的

如果当前要进行快照读的事务,就是更新该记录的事务

  • 也就是说,我要查看的这条记录,就是我自己插入/修改的
  • 那么我就应该看到这条记录 

如果我查看记录时,[创建/修改该记录的事务]比[我查看时正在执行最早事务]还要早出现,并且没有出现在我维护的活跃列表中

  • 存在过且没有在运行,说明[创建/修改该记录的事务]已经提交了,并且是在我查看之前就已经提交了
  • 我就应该能看见这条记录  

只要我能看到这条数据,也就没必要继续遍历版本链

  • 因为后面的都是更老的数据

不应该看见的

如果一个事务对数据做出的修改/创建,是在我查看后,提交前进行的提交

  • 虽然它也会形成版本链,但在我遍历版本链时,我不应该看见这条记录
  • 因为事务的隔离性

图示 

源码

id就是每条记录中的id

  • 所以外层一定是在遍历版本链,然后将每个版本的事务id传进该函数

该函数被封装在readview这个类内部的,所以可以直接访问类内成员

过程

场景

假设有下列场景:

当事务4对数据进行修改,会形成一个该条记录的版本链:

事务2进行快照读时,会为该条记录创建一个读视图:

因为我们需要查看该条记录

  • 所以我们需要从最新的数据开始遍历版本链,直到我们能看见这个版本为止
比较步骤

首先要判断,事务2能否看见事务4修改后的数据

  • DB_TRX_ID=4

在我执行快照读时,它是否早在[我所能看见的执行中的最早事务]前就已经开启了 -- DB_TRX_ID < up_limit_id ?

  • 因为4>1,所以不是
  • 它比除我之外的这些执行中的事务要晚开启,是否仍在执行不清楚

在我执行快照读时,它有没有被启动 -- DB_TRX_ID < low_limit_id ?

  • 因为4小于5,所以有,并且它的事务id>我的事务id
  • 所以它是在我启动之后,进行快照读之前启动的

它是否在我执行快照读时还处于活跃状态(仍在执行) -- DB_TRX_ID 属于 活跃列表 ?

  • 因为2不在列表中,所以不是活跃状态
  • 也就是说,它在我执行快照读时已经提交了

所以,既然在快照前就已经提交了,那么当前事务可以看到事务4的更改

RC和RR的本质区别

引入

回头看看这个操作过程,事务4在提交后就能被同时执行的事务2看见

  • 这不就是我们RC隔离级别下的现象吗

实验

设置为RR模式

  • 如果使用快照读,没什么问题
  • 如果使用当前读,就可以读出事务1更新后的结果
  • 注意,这里我们的快照读操作在事务a更新前进行

再来一次测试

  • 区别仅在于第二次的快照读是在更新后进行
  • 这次得到的结果是最新的数据
总结

我们仔细思考一下,其实第二次测试的结果也是符合可重复读的特性的

  • 因为在我们事务2中,并不知道有没有事务对数据做修改
  • 我们能看到的是,在进行第一次查询后,所有查询到的结果都是相同的
  • 这不就是可重复读吗?
  • 第二次测试,和我们前面介绍的[可见性判断过程]是一样的 -- 在进行快照读前提交,我们是应该看见的,和什么时候开启事务无关

测试1的重点在于:

  • 创建读视图时,它将事务a纳入了活跃事务列表中
  • 所以不应该看见它更新的数据

而测试2:

  • 是在事务a结束后才创建读视图,所以活跃事务列表中并没有事务1
  • 那么就应该看见更新后的结果

所以,形成读视图的时机会影响快照读的结果

  • 也就是事务的可见性

介绍

在RR级别下:

  • 某事务首次使用快照读时,会创建一个读视图,将当前时刻的事务状态记录下来
  • 自此之后,该事务使用快照读时,使用的都是同一个读视图,内容是不变的
  • 所以该事务的可见性不变
  • 那么之后做的任何修改,该事务都看不见了;早于这之前的修改是可见的

而在RC级别下:

  • 每次快照读都会生成一个新的读视图,所以它的可见性一直在变化
  • 时间一直在向后走,所以每次生成的读视图都是比较新的,它就总是能看见历史提交
  • 比如,这次的读视图认为事务a在和它并发执行,看不见 ; 到了下一个时刻,事务a已经提交了,于是就能看见了
  • 这就是为什么在RC级别下,事务总是可以看见其他事务提交的更新
  • 所以,RC才会有不可重复读的问题

总结

正是因为有了MVCC,我们才能理解为什么可以实现读写并发,为什么隔离性可以让我们看到不同的数据

  • 隔离性,回滚本质都是用MVCC实现的

MySQL的多版本并发控制(MVCC)是一种并发控制机制,它主要是为了解决并发读写冲突的问题。在MVCC机制中,每个事务都可以看到数据库中的一个快照,这个快照是在事务开始时确定的。事务读取数据时,实际上是读取了该快照中的数据,而不是实际的数据。当事务需要修改数据时,MySQL会根据数据的版本号来判断是否可以进行修改。 MVCC的实现原理主要是在每一行数据后面保存多个版本号,并且还需要保存该版本对应事务ID。当开始一个事务时,MySQL会为该事务分配一个唯一的事务ID,该事务ID会被用于标记事务对应的数据版本号。当一个事务需要读取数据时,MySQL会根据该事务事务ID和版本号来判断是否允许读取该数据。如果该事务事务ID小于等于该数据的版本号,那么就可以读取该数据。如果该事务需要修改数据,则MySQL会为该数据在数据库中创建一个新版本,并将该新版本版本号和事务ID保存下来。这样,其他事务就可以继续读取原来的版本,而该事务则可以读取新版本并修改数据,从而实现并发控制。 需要注意的是,MVCC只能解决读写冲突的问题,而不能解决写写冲突的问题。此外,MVCC也会占用一定的存储空间,因为每个数据行都需要保存多个版本号和事务ID。因此,在使用MVCC机制时,需要注意存储空间和能方面的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值