Mysql的MVCC知多少(隐藏字段,undolog版本链和ReadView)

前言

其实自己之前对MVCC知之甚少,总觉得是一块很难啃的骨头,有点内惧,但当你真的掌握之后,就发现打开了一扇大门,豁然开朗,鸟语花香~~

一、什么是MVCC

MVCC(multiversion Concurrency Control)多版本并发控制。
见名知意,MVCC就是通过多个数据行的版本对实现数据库的并发控制的。所谓并发,就是在某些隔离级别下,可以看到被别人修改前或后的数据,这样在查询的时候就不用等别人更新完我才能看到或操作了。

MVCC在Mysql InnoDB中的实现主要是为了提供数据库的并发性能,使得即使有读写冲突,也能做到不加锁。其思想其实是乐观锁的一种实现方式。

而MVCC的工作就是:生成一个ReadView,通过ReadView找到符合条件的记录版本(历史版本由undo log构建)。
查询语句只能读到生成ReadView之前已提交事务所做的修改,在生成ReadView之前未提交的事务或者之后才开启的事务所做的修改是看不到的。
写操作针对的是最新版本,读记录和历史版本以及改动记录的最新版本并不冲突,也就是采用MVCC时,读写并不冲突。

二、快照读与当前读

这里记住10字真言:读为快照读,写为当前读。
所谓快照读就是读的是快照数据,快照数据的出现也是为了避免加锁从而提高并发。
既然是快照,那么快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。快照读的前提是隔离级别不能是串行级别,串行级别下的快照读会退化成当前读。
当前读就是读的最新的数据,读的时候要保证其他的并发事务不能修改当前的记录,会对读的数据进行加锁。

三、4种隔离级别与MVCC

Read Uncommitted(读未提交):可以读到未提交的数据,即当前读,直接读最新的即可。
Serializable Read(串行读):使用加锁的方式来访问,也是最新读,即当前读。
Read Committed(读已提交)和Repeatable Read(可重复读)都是读到已提交的事务修改过的数据,如果一个事务修改了记录但未提交,就不能读取到这条最新记录,所以核心问题是判断版本链中哪个版本对当前事务可见,而这就是ReadView要解决的问题。

所以,这四种隔离级别中,只有Read Committed(读已提交)和Repeatable Read(可重复读)是MVCC要解决的并发的读写冲突的问题。
其中,Read Committed(读已提交)是每次查询的时候都获取一次最新的ReadView
Repeatable Read(可重复读)是只在第一次查询时获取ReadView,后面再查询都复用这个ReadView。之所以只在第一次查询获取ReadView,是为了避免不可重复读,而不可重复读就是两次读取的结果不一致,为了解决这个问题,那就索性两次读都读一个数据,这样就避免了两次读的数据不一致。
这里我还是想补充一下4种隔离级别存在的三种并发问题,以及解决和未解决的并发问题:

隔离级别存在的并发问题已解决的并发问题
Read Uncommitted(读未提交)脏读、不可重复读,幻读脏写
Read Committed(读已提交)不可重复读、幻读脏写、脏读
Repeatable Read(可重复读)幻读脏写、脏读、不可重复读
Serializable Read(串行读)-脏写、脏读、不可重复读、幻读

其实通过MVCC机制还解决了可重复读的幻读问题,这里我就不想细说了。

四、MVCC实现

说了这么多,MVCC究竟是怎么实现的呢?前面也提到过:
MVCC的工作就是:生成一个ReadView,通过ReadView找到符合条件的记录版本(历史版本由undo log构建)。
总结下来MVCC的实现就是依靠三个关键字:隐藏字段,undolog版本链和ReadView来实现的。

4.1 隐藏字段和undolog版本链

隐藏字段主要包括:trx_idroll_pointer
trx_id : 每次一个事务对某条数据记录改动时,都会把事务ID赋值给trx_id 隐藏列。
roll_pointer:每次对某条数据记录改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到记录修改前的信息。
下面举个🌰:
当前有一张person表,有一条:
id=1,name=“韩立”,class=一班 的数据,这条数据的trx_id=8,下面对这条数据进行如下的修改:

发生时间顺序事务10事务20
1BEGIN;
2BEGIN;
3UPDATE student SET name=“韩跑跑” WHERE id=1;
4UPDATE student SET name=“厉飞雨” WHERE id=1;
5COMMIT;
6UPDATE student SET name=“张铁” WHERE id=1;
7UPDATE student SET name=“曲魂” WHERE id=1;
8COMMIT;

以上的操作流程对name 的每次修改都会记录一条日志,每条日志都有一个roll_pointer属性,将这些undo日志连起来就成为了个链表:
在这里插入图片描述
id=1的这条记录在2个事务中做了4次变更,每次变更就会把前一个旧值放到undo日志中,随着更新的次数增多,所有的更新记录即版本就被roll_pointer连成一个链表,我们称之为版本链,链头就是当前记录的最新值,每个版本中还包括生成该版本对应的trx_id(事务ID)。

4.2 ReadView

前面反复提到过ReadView是个什么玩意呢?
ReadView就是事务在使用MVCC机制进行快照读操作时产生的读视图。包括creator_trx_id,trx_ids,up_limit_id,low_limit_id

  • creator_trx_id:创建这个ReadView的事务ID。(只有在对表中的记录做INSERT,DELETE,UPDATE时才会为事务分配事务ID,只有读事务的话事务ID默认为0。)
  • trx_ids:表示在生成ReadView时当前系统中活跃的读事务的事务ID列表。
  • up_limit_id:活跃的事务中最小的事务ID。
  • low_limit_id:表示生成ReadView时系统中应该分配给下一个事务的ID值。low_limit_id是系统中最大的事务ID值,并不仅限于活跃的事务ID。

针对low_limit_id的说明:low_limit_id并不是trx_ids中最大值。假设现有id为1,2,3都未提交的事务,只有3提交了,提交之后,就剩下1和2是活跃的,3提交后有一个新的读事务生成了ReadView时,则trx_ids包括1和2,up_limit_id=1,low_limit_id=4。

有了ReadView,会按照下面的规则,遍历版本链中的版本是否可见。

  • 如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同(trx_id=creator_trx_id),则说明当前事务正在访问的就是自己修改过的记录,该版本可以被当前事务访问。
  • 如果被访问版本的trx_id属性值小于ReadView中的up_limit_id值(trx_id<up_limit_id),则说明生成该版本的事务在当前事务生成ReadView之前已经提交,该版本可以被当前事务访问。
  • 如果被访问版本的trx_id属性值大于或ReadView中的low_limit_id值(trx_id>=low_limit_id),则说明生成该版本的事务在当前事务生成ReadView之后才开启,该版本不可以被当前事务访问。
  • 如果被访问版本的trx_id属性值介于ReadView中的up_limit_idlow_limit_id之间(up_limit_id<trx_id<low_limit_id),则需要判断一下trx_id值是否在trx_ids列表中:
    • 如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被当前事务访问。
    • 如果不在,说明创建ReadView时生成该版本的事务已经提交了,该版本可以被当前事务访问。

五、说明

本文的知识点都是因为看了B站尚硅谷康师傅的Mysql课程,康师傅出品必属精品,等后面有时间我还会再刷两遍,真是受益匪浅,强烈安利众仙家~~

-----------------你知道的越多,不知道的越多--------------

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值