透彻解读MVCC

一.MVCC是什么?

     MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问.

为什么要有MVCC?
      在并发访问场景中,会出现以下三种情况
      读读、读写、写写
      读读不存在安全问题,写写必然需要加锁来保证数据安全,那MVCC其实就是大佬们不满意简单加锁来解决读写冲突的一种无锁手段.

MVCC本质是什么?
      其实就是通过一些手段(隐藏字段,日志,readview)和算法来判断事务间的可见性,从而解决脏读,不可重复读数据问题.

二.前置知识

在介绍MVCC之前,我们应该先清楚以下知识点.

1.并发访问可能带来的数据问题.

      (1)脏读:在一个事务中读取了另一个事务尚未提交的数据.
      (2)不可重复读:在一个事务中两次读取数据内容不一致,主要由update引起.
      (3)幻读:在一个事务中的两次读取数据数量不一致,主要由insert和delete引起.

2.读数据的两种方式.

(1)当前读:
      读取的是记录数据的最新版本,需要加锁
      举例:
      select … lock in share mode(共享锁)
      select … for update(排他锁)
      insert (排他锁)
      update (排他锁)
      delete (排他锁)

(2)快照读:
      读取的是记录数据的可见版本(可能不是最新的数据),不用加锁
      举例:
      简单的select操作(不包括 select … lock in share mode, select … for update)

3.数据库的隔离级别:

在这里插入图片描述

Read Uncommited

  • 可以读取未提交记录。此隔离级别,不会使用,忽略。

Read Committed (RC)

  • 针对当前读,RC 隔离级别保证对读取到的记录加锁 (记录锁),所以,在只有当前读的情况下不会有不可重复读问题. 在快照读情况下可能存在
  • 无论是当前读还是快照读,当前隔离级别都会有幻读问题(因为没有对范围加锁).

Repeatable Read (RR)

  • 针对当前读,RR 隔离级别保证对读取到的记录加锁 (记录锁),同时保证对读取的范围加锁,新的满足查询条件的记录不能够插入 (间隙锁),所以在只有当前读的情况下不会有幻读问题, 只有在当前读与快照读混合使用下,才可能存在.

Serializable

  • 从 MVCC 并发控制退化为基于锁的并发控制。部分快照读与当前读,所有的读操作均为当前读,读加读锁 (S 锁),写加写锁 (X 锁)。
  • Serializable 隔离级别下,读写冲突,因此并发度急剧下降,在 MySQL/InnoDB 下不建议使用

mysql中默认的隔离级别是RR,也就是可重复读.

三.MVCC组成.

MVCC由隐藏字段,undolog,readview三部分组成.

1.隐藏字段.
(1)DB_TRX_ID
      创建这条记录的事务id或者最后一次修改的事务id.
(2)DB_ROLL_PTR
      回滚指针,指向这条记录的上一个版本.
(3)DB_ROW_ID
      隐藏主键,如果没有主键会生成一个6字节的row_id.

举例:有一个user表,表中有三个字段 name,age,gender,现在新增一条name=zhangsan的记录,数据情况如下:
在这里插入图片描述

2.undolog
回滚日志,记录事务中的历史数据,便于在insert,update,delete操作之后进行回滚.

举例:还是上面那个表,现在将name=zhangsan改成name=lisi,那我们数据库中的数据则变为.
在这里插入图片描述
而历史数据则会存储到undolog日志中去,回滚指针会指向该数据存储位置.

注意:在undolog日志中会存储所有的历史数据,那岂不是会无限增长?所以会有一个单独的线程去回收清除不需要的日志

3.readview
     事务在进行快照读的时候产生的一个读视图.所谓的读视图只是包含了某些字段信息而已.
作用:
      其实就是为了进行判断多个事务间的可见性判断而存在的信息记录.
      readview包含以下三个字段信息.:
      (1)trx_list:
            一个数值列表,用来记录当前readview生成时刻存在的活跃事务(也就是尚未提交的事务)id.
      (2)up_limit_id:
            事务活跃列表中最小的事务id.
      (3)low_limit_id:
            在readview生成时刻尚未分配的下一个事务id.
重点:
      对于不同的隔离级别(RC和RR),其最主要的区别就是生成的readview的时机是不一样的.
      RC:每次进行快照读都会生成新的readview.
      RR:事务开启后第一次进行快照读的时候才会生成readview,以后的快照读都会使用第一次生成的readview(如果进行当前读,则会重新生成readview).

四.MVCC核心流程.

首先给一个案例,在RR隔离级别的情况下.
   现在要同时开启四个事务去执行更新操作,注意我上面所提到的,DB_TRX_ID(事务id)就是创建这条记录的id或最后更新这条数据事务的id,所以在事务t1,t2,t3,t4各自更新某条数据后,对应的数据的DB_TRX_ID字段就变成1,2,3,4
     在我进行如下操作的时候,事务t2在进行快照读的时候能看到t4更新的数据.
在这里插入图片描述
     但是当我修改事务操作顺序,如下图所示,那么t2在第二次快照读就看不到t4更新的数据.
在这里插入图片描述
     究竟是为什么呢?下面用mvcc进行刨析,相信看完就会理解.

首先在第一种情况下:
在这里插入图片描述
      t2在进行快照读的时候,会生成一个readview,而是否能看到个t4事务的个更新数据取决于可见性算法(下图黄框中所示).依次执行1,2,3判断逻辑.可知在t2进行快照读的时候是能够看见t4更新的数据的.
在这里插入图片描述
第二种情况:
在这里插入图片描述
      首先在t2进行第一次快照读的时候,生成的readview如下所示. 这里也显示出DB_TRX_ID的值,因为一会要用到.
在这里插入图片描述
      然后在t4进行事务提交后,会修改这条记录中DB_TRX_ID的值,修改为4
在这里插入图片描述
      那么在t2进行第二次进行快照读的时候,因为此时的隔离级别是RR,所以不会生成新的readview,会使用第一次生成的readview,此时继续用可见性算法进行判断,由第三个逻辑判断可知,在readview生成时刻,t4事务还是活跃状态,还没有提交,那么此时当前数据是看不到的,也就验证了前面的观点.
在这里插入图片描述
注意:
     在快照读的时候有时候会读的是历史数据,读的是哪里的历史数据?
      是undolog日志中的历史数据

五.如何解决幻读.

我们知道MVCC可以解决脏读和不可重复读,那幻读要怎么解决呢?

1.怎样才会产生幻读?
      在快照读和当前读混合使用的时候才会产生幻读,也就是说当事务中的读操作全部使用快照读的时候是不会产生幻读的.

      比如我们在一个事务中,需要先查询某条记录,做一些计算, 然后再进行更新.

1.select name,age from student where name='a'  (快照读)
2.update student set age=13 where name='a' (当前读)

为什么?
      因为在全部使用快照读的时候都读的第一个readview生成的历史数据,而当进行了当前读,就会重新生成readview,导致出现幻读.

2.解决方式.
      在第一次查询的时候就使用当前读,会对数据加上行锁和间隙锁,这样其他事务在进行插入的时候会阻塞,从而解决幻读.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员bling

义父,感谢支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值