【MySQL】MVCC浅析

作用和适用范围

MVCC只在可重复读和提交读两种隔离级别下工作。因为在未提交读的隔离级别下,总数读取最新的数据行,而不是符合当前事务版本的数据行,而可串行化则是通过加锁互斥来访问数据,因此不需要  MVCC  的帮助。MVCC是一种用来解决读-写冲突的无锁并发控制。

用人话来说,就是MVCC实现了读操作不用阻塞写操作,写操作不用阻塞读操作的同时,避免了脏读和不可重复读。


什么是可重复读和提交读?

提交读(也叫不可重复读):在这种隔离级别下,所有事务只能读取其他事务已经提交的内容。能够彻底解决脏读的现象。但在这种隔离级别下,会出现一个事务的前后多次的查询中却返回了不同内容的数据的现象,也就是出现了不可重复读。此外,这时大多数数据库的默认隔离级别,但是MySQL不是

可重复读:在这种隔离级别下,同一个事务中前后多次的读取到的数据内容是不变的。也就是某个事务在执行的过程中,不允许其他事务进行update操作,但允许其他事务进行add操作,造成某个事务前后多次读取到的数据总量不一致的现象,从而产生幻读。但是InnoDB存储引擎利用MVCC(多版本并发控制)解决了幻读的问题并且这是MySQL的默认隔离级别


MVCC如何实现可重复读和提交读?

MVCC会在每一行的数据后面增加两个隐藏的列(分别叫: DATA_TRX_ID 、 DATA_ROLL_PTR ),一列表示行的创建时间,一列表示行的到期时间(或者删除时间),这里的时间是用系统的版本号来确定,系统的版本号如何计算?每当开始一个新的事务,系统的版本号都会递增。每当一个事务开始时,就会获得当前的系统版本号并作为该事务的事务id(又叫做事务版本号),来和每行数据的末尾两列进行比较。

:这里我其实有个疑问,我看《高性能MySQL》里面对于MVCC是按照上面这么叙述的,但是按照网上有些地方的说法有些不一样,第一列保存事务id没有不同,不同之处在于第二列的内容,书上说保存到期时间,但是网上说这是一个回滚指针,指向undo日志里面的旧版本数据。具体是什么我暂时还不清楚。

然后简要讲一下可重复读里面,MVCC是如何工作的。

1、select:执行select操作的时候会首先根据以下两个条件来检查每一行的最后两列:

  • 前一列(记录创建时间)的创建时间是否小于等于当前事务版本号,表示数据是之前已经存在的或者是这个事务创建的。
  • 后一列(记录到期时间)的删除时间是否大于事务版本号(或者没定义),表示数据没过期。

2、insert:会给插入数据的后两列的前一列保存事务的版本号作为他的创建时间

3、delete:会给删除数据的后两列的后一列保存当前事务的版本号作为到期时间(删除时间)

4、update:相当于delete和insert的结合,首先插入一行数据,前一列保存事务版本号作为创建时间,同时原来的数据的行的到期时间修改为事务的版本号(也就是事务id)。


为什么可重复读和提交读都是使用MVCC,但效果不一样?

相信很多人都有这个疑问,之前说了MVCC实现了可重复读和提交读,但是实现的效果却不一样呢,因为上面讲的过程其实是比较浅显的过程,具体的过程其实更加复杂,下面简单讲讲我的理解:

根据之前"注"里面说的,第二列里面是个回滚指针,每当对某一行数据进行修改(比如update)之后,会把这一行的值拷贝到undo日志里面,然后把原来的值的前一列修改为当前事务的id,后一列回滚指针指向undo日志里面的老版本,如果不断的更新的话,就会形成一条链表结构,即一个版本链,通过它可以找到各个老版本的信息。

下面照片截取了https://baijiahao.baidu.com/s?id=1629409989970483292&wfr=spider&for=pc

比如,执行:

那么undo日志里面的内容是:

 

然后,我们还要了解一个概念:ReadView。这里面主要就是有个列表来存储我们系统中当前活跃着的读写事务,也就是begin了还未提交的事务。通过这个列表来判断版本链里面的某个版本是否对当前事务可见。已提交读和可重复读的区别就在于它们生成ReadView的策略不同:

简单来说就是:

  • 也就是说已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,
  • 可重复读隔离级别则在第一次读的时候生成一个ReadView,之后的读都复用之前的ReadView。

举个例子:

对于“已提交读”,比如事务id=100的事务,修改了name,但是事务还没有提交,因此,此时readview=[100],他生成的版本链如下:

然后另一个事务id=110的事务,发起了select,要查询id=1的name字段;

它进入版本链,首先是事务id=100的,此时readview=[100,110],发现reddview里面有100,说明100的事务还没有执行完,因为是已提交读,所以这个数据对别的事务不可见;

因此会顺着版本链往下找,找到了版本id为60的数据,小于readview里面100,可以,就会读取这个数据。

然后事务id为100的事务提交了,此时事务id=110的事务再次进行查询id=1的name,那么接下来就有不同了。

  • 如果事务隔离级别为“已提交读”,因为事务id=100的事务提交了,所以ReadView更新为[110],查询版本链,就可以查询到小于110的旧版本数据了,也就是说事务id=100的数据之前无效,现在有效了,也就是在这个事务的前后两次查询查询到了不一样的结果。
  • 如果是“可重复读”,那么ReadView不会更新,还是原来的[100],所以查询版本链,还是只能查询小于100的旧版本数据。即使事务id=100的事务已经提交了。

根据这个例子就可以看出正是因为ReadView的不同的更新策略,导致了MVCC在两种隔离级别下的不同效果。

 

 

参考网站:https://baijiahao.baidu.com/s?id=1629409989970483292&wfr=spider&for=pc

https://www.codercto.com/a/88775.html

https://www.zhihu.com/question/27876575

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值