MVCC 底层原理详解

大家好 我是积极向上的湘锅锅

基本概念

当前读

顾名思义,读取的就是当前最新的版本,而且读取的时候保证其他并发事务不会修改该记录,会对记录加锁(共享锁或者是排它锁),如:select … lock in share mode(共享锁),select …
for update、update、insert、delete(排他锁)都是一种当前读

快照读

简单的 select(不加锁)就是快照读,快照读读取的可能是最新版本,也有可能是历史数据

Read Committed:每个select都会产生一个快照读

Repeatable Read:开启事务后第一个select才是快照读的地方

Serializable:快照读会退化为当前读(加锁)

MVCC

全称Multi-Version Concurrency Control,多版本并发控制,指维护一个数据的多个版本,使得读写操作没有冲突,也就是不会产生读写锁冲突,而快照读为MySQL实现MVCC提供了一个非阻塞读功能,这给我们高并发性能提升不少

MVCC的具体实现,还需要依赖于数据库记录中的三个隐式字段、undo log日志、readView

接下来,我们再来介绍一下InnoDB引擎的表中涉及到的隐藏字段 、undolog 以及 readview,从
而来介绍一下MVCC的原理。

隐藏字段

MVCC 底层原理详解_第1张图片
举个例子当我们创建了这张表,在查看表结构的时候,只看得到这三个显示的字段,但是实际上除了这三个字段意外,InnoDB还会自动的给我们添加三个隐藏字段,如下:
MVCC 底层原理详解_第2张图片
而上面前俩个字段是肯定添加的,而第三个要看是否自己设置了主键,如果没有指定主键,则会添加该字段

undolog

回滚日志,在insert,update,delete的时候产生便于数据回滚的日志

  • 在insert的时候,产生的undo log 日志旨在回滚的时候需要,在事务提交后,可被立即删除
  • 而update,delete的时候,产生的undo log日志不仅在回滚时需要,在快照读时也需要,不会立即被删除

版本链

为什么叫版本链,慢慢往下看…

有一张表的原始数据为:
MVCC 底层原理详解_第3张图片

DB_TRX_ID : 代表最近修改事务ID,记录插入这条记录或最后一次修改该记录的事务ID,是自增的

DB_ROLL_PTR : 指向上一个版本,由于这条数据是才插入的,没有被更新过,所以该字段值为null。

然后,有四个并发事务同时在访问这张表

第一步

MVCC 底层原理详解_第4张图片
当事务2开始修改数据时,会产生undo log日志,记录数据变更之前的样子,并且会记录本次操作的事务id
MVCC 底层原理详解_第5张图片

第二步

MVCC 底层原理详解_第6张图片
当事务3执行第一条修改语句时,也会记录记录undo log日志,记录数据变更之前的样子,然后更新版本记录,并且记录本次操作事务id
MVCC 底层原理详解_第7张图片

第三步

MVCC 底层原理详解_第8张图片
当事务4执行第一条修改语句时,也会记录undo log日志,记录数据变更之前的样子; 然后更新记
录,并且记录本次操作的事务ID
MVCC 底层原理详解_第9张图片
最终我们发现,不同事务或相同事务对同一条记录进行修改,会导致该记录的undolog生成一条
记录版本链表,链表的头部是最新的旧记录,链表尾部是最早的旧记录

Readview

Readview(读视图)是快照读SQL执行的时MVCC提取数据的依据,记录并维护系统当前活跃的事务(未提交的)id
Redaview中包含了四个核心字段:
MVCC 底层原理详解_第10张图片
看图有点抽象,我觉得可以这样理解

  • m_ids:当前活跃的事务ID集合 ==>当前并发过程中还未提交的事务ID集合
  • min_trx_id:最小活跃事务ID ==>在还未提交的事务ID集合中最小那个
  • max_trx_id:预分配事务ID,当前最大事务ID+1 ==>也就是说下一个事务ID应该是当前还未提交的事务id集合中最大那个ID+1
  • creator_trx_id:ReadView创建者的事务ID ==>这个好理解 就是哪个事务创建的这个ReadView那就是那个事务的ID
  • 如果还有疑问下面还有图帮助理解哦,觉得有帮助请点个赞

而在readview中就规定了版本链数据的访问规则
trx_id 代表当前undolog版本链对应事务ID
MVCC 底层原理详解_第11张图片
看这可能有的小伙伴有点懵 我对这张图解释一下,不对之处请指出

  • trx_id 可以理解为每一条匹配的undo log版本链的事务ID,也就是哪个事务ID产生的这条记录
  • 是从版本链尾部到头部开始匹配
  • 如果trx_id == creator_trx_id那就说明改记录是自己所在的事务更改的,是一家人,那肯定可以访问该版本
  • 如果trx_id < min_trx_id,也就是说访问的这个事务已经提交了,因为目前还未结束的事务ID都比它大,那就可以访问该版本
  • 如果trx_id > max_trx_id,那就说明已经有事务在生成ReadView之后开启了,我们肯定不能访问该版本,防止产生重复读的问题
  • 如果min_trx_id <= trx_id <=max_trx_id,如果trx_id不在m_ids集合里面,那就说明该事务已经提交了,提交了我们肯定可以访问,如果没有提交,那就不能访问

不同的隔离级别,生成ReadView的时机不同

  • READ COMMITTED :在事务中每一次执行快照读时生成ReadView。
  • READ REPEATABLE:仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。

原理分析

1.RC隔离级别

RC隔离级别下,在事务中每一次执行快照读时生成ReadView。

我们就来分析事务5中,两次快照读读取数据,是如何获取数据的?
在事务5中,查询了两次id为30的记录,由于隔离级别为Read Committed,所以每一次进行快照读
都会生成一个ReadView,那么两次生成的ReadView如下:
MVCC 底层原理详解_第12张图片
这里有图也可以好好理解一下ReadView每一个字段所代表的具体含义
那么俩次快照读在获取数据的时候,就需要根据所生成的ReadView和ReadView的版本链访问规则,到undo log版本练中匹配数据,最终决定此次快照读使用哪一条记录的数据

A.先来看第一次快照读具体的读取过程
MVCC 底层原理详解_第13张图片
下图是版本链和访问规则
MVCC 底层原理详解_第14张图片在进行匹配时,会从undo log的版本链,从上到下进行挨个匹配:

  • 先匹配这条记录MVCC 底层原理详解_第15张图片
    这条记录对应的trx_id为4,也就是将4带入右侧的匹配规则中
    ①不满足 ②不满足 ③不满足 ④也不满足 ,
    都不满足,则继续匹配undo log版本链的下一条

  • 再匹配第二条在这里插入图片描述
    这条记录对应的trx_id为3,也就是将3带入右侧的匹配规则中。
    ①不满足 ②不满足 ③不满足 ④也不满足
    都不满足,则继续匹配undo log版本链的下一条

  • 再匹配第三条在这里插入图片描述
    这条记录对应的trx_id为2,也就是将2带入右侧的匹配规则中。
    ①不满足 ②满足
    终止匹配
    此次快照读,返回的数据就是版本链中trx_id为2记录的这条数据

B. 再来看第二次快照读具体的读取过程
MVCC 底层原理详解_第16张图片
上图中注意第二次快照读是下面那个ReadView
MVCC 底层原理详解_第17张图片

  • 先匹配这条记录
    MVCC 底层原理详解_第18张图片
    这条记录对应的trx_id为4,也就是将4带入右侧的匹配规则中
    ①不满足 ②不满足 ③不满足 ④也不满足
    都不满足,则继续匹配undo log版本链的下一条

  • 再匹配第二条
    在这里插入图片描述
    这条记录对应的trx_id为3,也就是将3带入右侧的匹配规则中。
    ①不满足 ②满足
    终止匹配
    此次快照读,返回的数据就是版本链中记录的这条数据

2. RR隔离级别

RR隔离级别下,仅在事务中第一次执行快照读时生成ReadView,后续复用该ReadView。 而RR 是可
重复读,在一个事务中,执行两次相同的select语句,查询到的结果是一样的
那在MVCC的机制下,是如何防止可重复读的呢?
还是用事务5举例
MVCC 底层原理详解_第19张图片
我们看到,在RR隔离级别下,只是在事务中第一次快照读时生成ReadView,后续都是复用该
ReadView,那么既然ReadView都一样, ReadView的版本链匹配规则也一样, 那么最终快照读返
回的结果也是一样的

结论

所以呢,MVCC的实现原理就是通过 InnoDB表的隐藏字段、UndoLog 版本链、ReadView来实现的,而MVCC + 锁(next-key ),则实现了事务的隔离性。 而一致性则是由redolog 与 undolog保证

你可能感兴趣的:(MySQL,mysql,数据库,java)