MVCC原理机制

作者: 迈莫coding | 来源:发表于2021-01-16 11:22 被阅读0次

    目录

    • 背景
    • 前提回顾
      • 什么是MVCC
      • 什么是当前读和快照读
      • 当前读,快照读和MVCC关系
      • MVCC,乐观锁,悲观锁关系
    • MVCC实现原理
    • 整体流程
    • 彩蛋
    • RR和RC隔离级别下的InnoDB快照读有什么区别
    • 闲聊
      -【迈莫coding】

    背景

    写<<校招MySQL那些事>>系列文章,一方面帮助在校大学生可以提早知道大厂的面试过程,了解大厂究竟需要什么样人才,自己该如何进行准备,全力以赴;另一方面也是自我巩固知识,因为我也是从校招进入大厂的,自己也经历过痛并快乐的过程,也希望可以把我自己的经验,都吐露出来。

    接下来我会写很多关于校招文章,比如<<校招Redis那些事>>,<<校招go那些事>>,<<校招kafka那些事>>等等,敬请期待。

    前提回顾

    什么是MVCC

    多版本并发控制(MVCC),是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务戳关联,读操作只读事务开始前的数据库的快照。这样在读操作的时候不会阻塞写操作,写操作不会阻塞读操作的同时,也避免了脏读和不可重复读。

    什么是当前读和快照读

    在学MVCC多版本并发控制之前,先了解一下什么是mysql InnoDB下的当前读和快照读?

    • 当前读
      当前读指的就是它读取的记录是最新版本的。由于它要读取记录的版本是最新版本,所以读取时须保证其他事务不能修改当前记录,因此需要对读取的记录进行加锁。
    • 快照读
      快照读可以理解为不加锁的select操作就是快照读;快照读的前提是隔离级别不是串行级别,因为在串行隔离级别,快照读可以理解为当前读;快照读的出现,主要解决了在不加锁的情况下也可以进行读取,降低了锁开销;它的实现是基础多版本并发控制,即MVCC;由于它是基于多版本并发控制,所以使用快照读读取的记录并不一定是最新记录。

    当前读,快照读和MVCC关系

    • 准确的说,MVCC主要基于"维护一条数据的多个版本,进而保证在读操作的同时不会阻塞写操作,写操作的同时也不会阻塞读操作"
    • 快照读其实就是MVCC的一种体现方式,进行非阻塞读。而相对而言,当前读就是悲观锁的体现,每次进行查询操作时,mysql都认为其是不安全操作,为其加锁保证安全,但每次读取的数据为最新数据
    • MCC模型在mysql中的具体实现主要是由隐藏字段,undolog,read-view等去完成的,具体实现逻辑看下面的MVCC实现原理

    MVCC,乐观锁,悲观锁关系

    • MVCC+乐观锁
      MVCC解决读写冲突,乐观锁解决写写冲突
    • MVCC+悲观锁
      MVCC解决读写冲突,悲观锁解决写写冲突

    这样组合的方式最大程度的提高数据库并发性能,解决读写冲突以及写写冲突所导致的问题。

    MVCC实现原理

    MVCC就是多版本并发控制,用来解决读写冲突。它主要由隐藏字段,undolog日志,read-view来配合完成的。所以先看看这几个属性,理解其含义,便于理解MVCC实现原理。

    隐藏字段

    隐藏字段中除了咱们自定义的字段外,还隐含着其他属性字段,是系统默认给加上去的,比如roll_pointer,trx_id等字段。
    roll_pointer
    回滚指针,指向这条记录的上一个版本
    trx_id
    事务ID,记录创建/修改这条记录的事务ID,用于版本比较,从而找到快照

    name age trx_id roll_pointer(回滚指针)
    迈莫 22 1 0x1654u

    如表中所示,name和age属性为用户自定义属性,而trx_id和roll_pointer就表示隐藏属性,数据库默认添加。在这表中,trx_id表示操作这条记录的事务ID,roll_pointer是回滚指针,表示指向上一个版本,一般配合undolog日志使用

    undolog日志

    undolog日志存储某条记录的所有操作,以链表方式将各个版本进行串联起来

    举例

    第一步: 比如有个事务1向person表中插入一条数据,记录如下,name为迈莫,age为22,事务ID为1,回滚指针假设为null,如下图所示
    [图片上传失败...(image-52ed3b-1610512344090)]

    第二步: 此时又来一个事务2,对该记录的age值进行修改,修改为23
    首先将事务1的操作记录添加到undolog日志中
    将事务2的操作记录作为数据的最新记录
    将事务2中隐藏字段roll_pointer(回滚指针)指向事务1,进行串联

    在这里插入图片描述

    第三步: 又有一个事务3,对该记录的name值进行操作,修改为memolei
    首先将事务2的操作记录迁移到undolog日志中
    将事务3的操作记录作为数据的最新记录
    将事务3中隐藏字段roll_pointer(回滚指针)指向事务2,进行串联

    在这里插入图片描述

    从上面图可以看到,每当有事务对该数据进行操作时,首先会将操作前最新数据迁移到undolog日志中,将当前事务操作的记录作为最新数据,并且将隐藏字段roll_pointer指向上一个版本

    read-view(一致性视图)

    当事务第一次执行查询sql时会生成一致性视图read-view,它由执行查询时所有未提交事务ID数组(数组里最小的ID为min_id)和已创建的最大事务id(max_id)组成,查询的数据结果需要跟read-view做比较从而得到快照结果

    版本链比对规则:

    • 如果落在绿色部分(trx_id<min_id),表示这个版本是已提交的事物生成的,这个数据是可见的
    • 如果落在红色部分(trx_id>max),表示这个版本是由将来启动的事务生成的,是肯定不可见的
    • 如果落在黄色部分(min_id<=trx_id<=max_id),那就包括两种情况
      1. 若row的trx_id在数组中,表示这个版本是由还没提交的事务生成的,不可见,当前自己的事务是可见的
      2. 若row的trx_id不在数组中,表示这个版本是已经提交的事务生成的,是可见的

    整体流程

    前提条件

    在这里插入图片描述

    如图表所示,有四个事务,分别为translation2,translation3,translation4,translation5;translation2,translation3,translation4分别对数据库进行修改操作,translation5进行查询操作。

    执行过程

    1. 当translation2对persion表中ID为1的数据进行修改时,首先会将该记录操作作为最新记录,并且roll_pointer指向上一个版本,但尤于translation2未commit,所以translation2仍然为未提交事务


      在这里插入图片描述
    2. 当translation3对persion表中ID为1的数据进行修改时,首先会将该记录操作作为最新记录,并且roll_pointer指向上一个版本,但尤于translation3未commit,所以translation3仍然为未提交事务


      在这里插入图片描述
    3. 当translation4对persion表中ID为1的数据进行修改时,首先会将该记录操作作为最新记录,并且roll_pointer指向上一个版本,但尤于translation4已commit,所以translation4为已提交事务


      在这里插入图片描述
    1. translation5进行select语句查询时,并且由于其是第一次创建查询语句,所以会创建read-view一致性视图。read-view一致性视图是由未提交事务ID数组和最大事务ID组成,由表中可知,当translation5进行查询时,translation2和translation3都为未提交事务,translation4为已提交事务,所以,read-view中未提交事务ID数组由translation2和translation3组成,最大事务ID为translation4,所以max_id为translation5,min_id为translation2
      [图片上传失败...(image-bf670e-1610512344090)]

    2. 接下来的话,就需要进行版本链比较(若不记得版本链比较规则回去温习一下),由于read-view由未提交事务数组[1,2]和最大事务ID3组成。由于当前最新记录事务ID为4,4大于最大事务ID3,所以无法查看;进行回溯,到undolog日志查询,事务3在未提交事务数组中,也就是中间这一段,由于事务三不在未提交事务数组中,说明事务3为已提交事务,因此是可见的,最终结果为name="memolei"


      在这里插入图片描述

    到此,MVCC整个流程处理完了,一遍生,二遍熟,温故而知新。

    彩蛋

    RR和RC隔离级别下的InnoDB快照读有什么区别

    • RR隔离级别下,当事务第一次进行快照读,仅此一次创建read-view视图,所以read-view中的未提交事务数组和最大事务ID始终保持不变,因此每次读取时只会读取事务之前的数据
    • 而在RC隔离级别下,每次事务进行快照读时,都会生成read-view视图,导致在RC隔离级别下事务可以看到其他事务修改后的数据,这也是导致不可重复的原因

    总之在RC隔离级别下,是每个快照读都会生成并获取最新的Read View;而在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View。

    闲聊

    • 读完文章,思考一下自己和MVCC的cp率是不是又提高了
    • 我是迈莫,欢迎大家和我交流

    文章也会持续更新,可以微信搜索「 迈莫coding 」第一时间阅读。

    相关文章

      网友评论

        本文标题:MVCC原理机制

        本文链接:https://www.haomeiwen.com/subject/abeuaktx.html