三、一文搞懂MySQL MVCC

一、背景

1、事务

  • MySQL的 InnoDB引擎提供了事务,事务的四大特性:
    ①原子性:要么全部成功,要么全部失败,如果事务中间有一个步骤失败了,会回滚。
    ②一致性:也即数据一致性,一次事务过后,所有数据都应该是正确的,不存在加的没加,减的没减。
    ③隔离性:多个并发事务对数据进行修改时,不会相互影响,导致出现a的事务中的数据被b的事务修改的现象。
    ④持久性:事务结束后,数据被永久保存,不会丢失。
  • InnoDB是如何保证四大特性的:
    ①原子性通过undo log(回滚日志)保证 。
    ②隔离性通过MVCC和锁保证 。
    ③持久性通过redo log(重做日志)保证。
    ④一致性有原子性+隔离性+持久性保证。

2、并发环境下事务所引发的问题

在MySQL中,如果同时存在多个事务,可能会发生脏读、不可重复读和幻读的问题。

  • 脏读:一个事务读到了另一个事务未提交的数据。例如a事务更新金额,之后b事务此时读取到a的金额,最后a事务回滚,b事务此时读到的就是过期的数据,也就是脏数据。
  • 不可重复读:一个事务多次读同一个数据(这里强调同一个字段),前一次读的和后一次读的不一样。例如,a事务读取金额为100,然后b事务更新金额为200并提交了事务,之后a事务又读取金额,变为了200。不可重复读强调的数据的值发生了变化
  • 幻读:一个事务多次读一个范围内的数量,前后两次的数量不一致。例如,a事务读取金额大于1万的用户数量为100个,然后b事务插入了20个金额大于1万的用户数,并提交了事务。之后a事务再次读取金额大于1万的用户数量,得到了120个。幻读强调数据的前后读取到的数量不一致。

以上三种问题的严重性排序为脏读>不可重复读>幻读

3、事务隔离级别

针对以上三种问题,SQL标准提出了四种隔离级别来规避这种现象。分别是

  • 读未提交(RU,read uncomitted),指一个事务还没提交,他的修改就能被其他事务看到;
  • 读已提交(RC,read committed),指一个事务提交后,他的修改才能被其他事务看到;
  • 可重复读(RR,repeatable read),指一个事务执行过程中看到的数据始终是一致的;InnoDB默认
  • 串行化(serializable),直接对记录加读写锁,如果发生读写,其他事务需要等待前一个事务完成。

以上四种隔离级别的隔离强度越高,性能越低,强度排序为串行化>可重复读>读已提交>读未提交。

在四种隔离级别下,能解决的问题类型也不同;

  • 读未提交:所有问题都没解决。
  • 读已提交:解决了脏读。
  • 可重复读:解决了脏读,不可重复读。
  • 串行化:所有问题都可以解决。

不同厂商对SQL标准的支持不一样,MySQL的innodb引擎默认下的可重复读没有完全解决幻读问题。而是通过MVCC+临键锁的方式来尽量避免,但是在极端情况下还是没有解决的(其实在可重复读的情况下,如果事务在第一次读和第二次读的字段不是同一个,其实也没解决不可重复读的问题,这里只能看不可重复读的定义了,如果不可重复读指的是读同一个字段,那就解决了,如果前后读的不是相同字段,那还是没解决,后面会有例子。)

4、快照读和当前读

  • 快照读:读取快照生成时的数据,快照是什么?在下面MVCC会讲到,普通的select语句都是快照读。
  • 当前读:读取当前最新的数据,select …for update、select… locki n share mode 、update是当前读。

5、innodb避免幻读的方式

  • 针对快照读:通过MVCC,使用版本链快照的方式,每次快照读都是读取快照生成时的数据,这就使得前后读取的数据量是一致的。
  • 针对当前读:通过临键锁(next-key lock)的方式,锁定临键锁所锁定的范围(具体什么范围可以看我的《mysql中的锁》文章),来阻止其他事务对改范围内的数据进行插入修改的操作,从而使得前后数据量一致。

为了解决并发读写的环境下,为保证数据的一致性,需要加锁,但加锁又会影响读写效率,因此引入MVCC,通过维护一个数据的多版本链,使得读写没有冲突,从而提高并发下的读写效率。

二、MVCC

1、什么是MVCC?

MVCC全称多版本并发控制,它通过维护一个称为Read View(读视图)的数据,以及维护了在表中的两个隐藏列来配合不同隔离级别下的事务中的快照读,从而避免读取到的数据出现脏读、不可重复读问题。

2、MVCC具体是如何实现的?

这里只做简单阐述,

  • 首先读视图内定义了四个字段,他们主要记录了一系列的事务id,而每次在读视图生成时,就不可改变;
  • 表中的两个隐藏列分别记录了当前对改行进行修改的事务id,和一个指向上一个版本行数据的指针,每次修改完后,都会把上个版本信息在undo log日志中记录下来,而这列的指针则指向undo log日志中上个版本的信息,从而形成一条包含所有修改记录的链表,即版本链。

MVCC通过读视图中的四个字段与隐藏列中的事务id比对,保证了事务中的快照读操作读到的数据是版本链上自己事务开始时的数据,从而避免了读取到其他事务所修改的数据,进而避免了脏读、不可重复读的问题。

3、什么是快照?

  • 快照是MVCC通过读视图、版本链所实现的在某一时刻下的一份数据副本,这里的某一时刻可以是事务开始时,或者是每次快照读、当前读开始时。

4、不同级别下的快照读

其中,要注意的是,在RC级别和RR级别下的快照读是不一样的(RU级别和串行化下不考虑,因为没用到MVCC,RU都是当前读,串行化都是用锁)。

  • 在RC中,事务开始时以及每次读取操作时(包括快照读和当前读)都会生成一个快照副本,这个快照副本所获取到的数据都是已提交的数据,从而避免了脏读的发生。但是,因为每次读取都会生成新的快照,如果第一次读取和第二次读取之间其他事务提交了修改,就会导致第二次读取的数据和第一次不一致,从而引发不可重复读和幻读的问题。
  • 在RR中,事务开始时会生成一次快照副本,之后的所有快照读都不会再生成新的快照副本,而是沿用第一次的快照副本,从而保证了事务的整个过程读取到的数据都是最开始那份,因此避免了不可重复读和幻读的问题。

5、不同级别下的当前读

  • 在RC中,所有事务在进行当前读时都会上行锁,事务提交后释放锁,因此读取到的都是最新提交的数据,因此能够解决脏读的问题,但避免不了不可重复读和幻读。
  • 在RR中,每次当前读都会加上临键锁,从而使得记录前后都不允许插入或修改,进而避免了不可重复读和幻读,

6、不可重复读和幻读在RR级别中出现的场景。

  • 如果在一次快照读之后进行了当前读,在这之后的快照读都将读取最新生成的快照,而不是事务开始时的快照,如果不可重复读针对一条记录中的某个字段,那么RR级别下不会出现不可重复读,但如果是针对多个字段,那么在这种快照读->当前读->快照读的场景就会出现不可重复读。如图
    在这里插入图片描述
  • 同理,在快照读->当前读->快照读的场景下也会出现幻读。

三、总结

MVCC是Mysql的innoDB引擎的在RC和RR隔离级别下,针对快照读,为了避免多个事务在并发过程中,所出现的不可重复读和幻读的问题的一套解决方案,它通过一个称为读视图的数据结构以及在表中的两个隐藏列,对多个事务之间的关系进行处理,保证了一次事务过程中多次快照读得到的结果是一致的,从而在RC中避免了脏读,在RR中避免了不可重复读及幻读的问题。但是,如果在一次事务中多次快照读之间存在当前读,就会导致重新生成快照,从而仍然会引发幻读的问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值