mysql可重复读防幻读吗_深刻学习InnoDB可重复读隔离级别下如何避免幻读

1、InnoDB可重复读隔离级别下如何避免幻读

在理解什么是幻读以前,先了解下脏读、幻读、不可重复读在实操场景中的现象。web

脏读:指的就是一个事务读取到了另外一个事务还未提交的数据,当该事物将数据回滚,则读取到的就是脏数据。

脏读形成的结果:事务拿着脏的数据(还未提交的数据,若是回滚了)去执行业务操做,会影响业务。

脏读解决方案:将数据库事务隔离级别改成RC,因此事务只能读取到其余事务已经提交的数据。面试

不可重复读:指的是对于两个事务A、B,A事务进行查询操做,B事务进行更新操做,更新了数据,而且提交了B事务。此时A事务再次来查询该记录,会发现和以前查询的结果不同了,以A事务的角度来讲,A事务什么都没操做(也不知道其余事务是否有操做)就发生了数据改变,即两次读是不重复的,这就表明了不可重复读。

不可重复读形成的结果:同一事务中两次读取的结果不同。

不可重复读解决方案:解决方案就是将事务隔离级别由RC提高为RR。若是将事务隔离级别提高为了RR,则无论B事务如何更新数据,A事务中读取的数据都是相同的,另外彻底不用担忧在A事务中进行操做,会形成数据不一致的问题,由于在A事务中若是进行了数据的修改操做,会使用的B事务更新以后的数据来进行修改操做。数据库

幻读:(幻读的复现须要将事务隔离级别下降为RC,由于在InnoDB中RR已经解决了幻读现象)一样的对于两个事务A、B。如今数据库表中有3条记录,A事务将要将更新全部的记录,于此同时B事务新增了一条记录,并提交了事务。以后A事务执行了update操做,会发现成功操做4条记录,从A的角度来讲,我什么都没作,怎么就变成了4条记录了呢?这就是幻读现象。【幻读侧重于对于记录的增长以及删除现象,而不可重复读侧重于记录自己的数据】

幻读形成的结果:同一事务中记录条数不一样,产生幻觉。

幻读解决方案:InnoDB的伪MVCC机制。并发

表象:快照读(非阻塞读)–伪MVCC

内在:next-key锁(行锁+GAP锁也就是间隙锁)svg

什么是当前读呢?能够简单理解为,加了锁的增删改查就是当前读。3d

当前读(查询):select…lock in share mode,select…for update日志

当前读(增、删、改):update、insert、deletexml

无论上的是X锁(排他锁)仍是S锁(共享锁)都为当前读。当前读是啥意思呢?意思是当前操做的是最新记录,其余的并发事务不能修改当前记录,对当前记录加锁。其中,select…lock in share mode是使用的S锁,select…for update、update、insert、delete都是使用的X锁。对象

共享锁和排他锁的区别:blog

共享锁(s):又称读锁。容许一个事务去读一行,阻止其余事务得到相同数据集的排他锁。若事务T对数据对象A加上S锁,则事务T能够读A但不能修改A,其余事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其余事务能够读A,但在T释放A上的S锁以前不能对A作任何修改。

排他锁(X):又称写锁。容许获取排他锁的事务更新数据,阻止其余事务取得相同的数据集共享读锁和排他写锁。若事务T对数据对象A加上X锁,事务T能够读A也能够修改A,其余事务不能再对A加任何锁,直到T释放A上的锁。

使用下图理解当前读在MySQL内部运行机制

79a5f5d67d2fe9f6ef4ccf1a38bcbedc.png

而快照读是什么呢?快照读就是不加锁的非阻塞读,也就是select操做。不过这里的快照读是基于非Serializable事务隔离界别下的,由于在Serializable隔离级别下,快照度会退化为当前读。这里的快照读就是MVCC的实现机制。

这里因为篇幅缘由,没有贴出实验过程,这里作简单总结:

在RC隔离界别下,当前读和快照读读取的都是同一版本。

在RR隔离界别下,当前读读到的是最新版本数据,而快照度可能读取到的是历史版本数据。

那么在RR隔离级别下,何时能够读到最新版本数据呢?若是在进行增、删、改完成以后,再去查询快照读,则此时读取到的是最新版本的数据。若是是在增、删、改以前进行了快照读,在增、删、改以后继续快照读,则读到的就是旧版本数据。

总结:快照读取决于一开始快照读的时间。

2、RR级别下的InnoDB的(快照读)非阻塞读是如何实现的?

底层实现离不开数据行里的DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID字段,除此以外还须要undo日志,以及read view。

原理实现就是下列几个关键内容:

数据行里的DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID

undo日志

read view机制

提及DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID,那就要先知道MySQL一条记录是由记录的额外信息部分和记录的真实数据两部分组成。记录的额外记录部分存有变长字段长度列表、NULL值列表等,而记录的真实数据部分又由真实数据以及DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID这三个隐藏列组成。

e0da1f6807467f7bc6e0c67760c1f196.png

80ec187763dbffbcb639a51b2b75537e.png

好比如今有一个记录Field一、Field二、Field3数据分别为十一、十二、13,如今事务要修改该记录,将Field2修改成32。则这条记录首先会加载X锁,首先undo log中会拷贝一条修改前的记录,并赋值DB_ROW_ID。此时被X锁锁住的记录的DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID分别进行赋值,而且DB_ROLL_PTR的记录会指向undo log中的DB_ROW_ID的值。

若是此时又有一个事务对该记录进行了修改,则undo log日志中又会增长一条日志。

3cd259d8bd6c9416820f74fd696a5ce6.png

这样就是快照读版本的实现了。

3、InnoDB如何在RR隔离界别下避免幻读——next-key锁

其实,真正实现RR隔离级别下的幻读现象,是由next-key锁解决的。next-key锁又分为了(行锁 + gap锁)

3.1 行锁

行锁就是Record Lock,就是对单个行记录加的锁。X锁和S锁就是行锁。

3.2 Gap锁

Gap就是索引树种,插入新数据的间隙。间隙锁即锁定一个记录的范围,可是不锁定记录自己。间隙锁是为了不同一事务的两次当前读出现幻读的状况。须要注意的是,Gap锁在RU、RC隔离级别下时不存在的,在RR、Serializable隔离级别下都只支持Gap锁。这就是为何RU、RC隔离级别下没法避免幻读,RR、Serializable可以避免幻读的缘由。

下面讨论的都是在RR隔离级别下出现Gap锁的场景。

在RR隔离级别下,不管删、改、查,当前读若用到主键索引或者惟一键索引,会使用Gap锁吗?

答:若是where条件所有命中,则不会用Gap锁,只会加记录锁。

怎么去理解where条件所有命中,不用加Gap锁只须要加记录锁就好了呢?这是由于好比A事务须要修改操做全部记录,此时B事务使用主键索引id来进行where条件查询来进行删除操做,此时只须要锁住where命中的id记录便可,那么就能防止事务A出现幻读现象。

4ec77e463c188f80ed0b67081187eb68.png

如图,tb中name为主键索引,id为惟一索引。某个事务使用delete from tb where id = 9进行删除操做,首先where条件所有命中,因此先会为id为9的这个记录的惟一索引加上行锁,而后会为name为d的主键索引(聚镞索引)加上排他锁。这是为了防止其余事务对where name = 9进行操做,致使数据不一致的状况。

在RR隔离级别下,不管删、改、查,当前读若用到主键索引或者惟一键索引,且若是where条件部分命中或者全不命中,则会加Gap锁。对于这种状况,就包含了范围查询以及精确查询非所有命中的状况。

例子1:好比如今事务A要删除一条不存在的id为7的记录,此时事务B要新增一条id为8的记录,会发现事务B一直处于等待中,这是由于精准查询所有都不命中,会对该记录范围加Gap锁。

例子2:【tb_student中存在id为5,6,9的学生】好比在事务A中使用语句select * from tb_student where id in (5,7,9) lock in share mode;使用当前读(共享锁)来查询学生信息。在另一个事务B中去进行新增id为6,7,8的学生,发现事务一直在等待中。这里是由于where id in (5,7,9)部分命中,因此会为(5,9]加Gap锁,锁的范围为左开右闭。所以事务B新增id为7,8的记录会被Gap锁锁住,这就是精准查询不所有命中的状况。

Gao锁会用在非惟一索引或者不走索引的当前读中

非惟一索引

8b6cb6c6169eac8a2b3dc823a126bc67.png

好比图中某一事务A执行delete from tb1 where id = 9,由于id是非惟一索引,若是没有加Gap锁,在事务B新增一条id为9的记录时,A事务执行完delete语句后,就会发现成功删除3条记录,出现了幻觉,因此给id为9的记录加上Gap锁来防止幻读的发生。

至于Gap锁的范围,如上为:(-∞,2], (2, 6], (6, 9], (9, 11], (11, 15], (15, +∞)中的 (6, 9], (9, 11]

不走索引

对于不走索引的状况,InnoDB会为全部的Gap加锁,至关于锁表。

5b4d4722f5815cdab384ea5aff6de824.png

4、总结

4.1 InnoDB在RR隔离级别下是如何实现幻读问题的解决的呢?

表象:快照读(非阻塞读),伪MVCC

底层:next-key(行锁+Gap锁)

a. 在RU、RC隔离级别下不存在Gap锁,因此在RU、RC隔离级别下没法解决幻读;在RR、Serializable隔离级别下都实现了Gap锁,因此解决了幻读现象。

b. 在RR隔离级别下,若是删、改、查语句的where条件走的是主键索引或者惟一索引

i. where条件所有命中,则给该记录加上记录锁。

ii. where条件不所有命中,则给该记录周围加上Gap锁。

iii. 加上记录锁或者是Gap锁都是为了防止RR隔离级别下发生幻读现象。

c. 在RR隔离级别下,若是删、改、查语句的where条件没有走索引或者是非惟一索引或非主键索引

在当前读where条件若是没有走非惟一索引或者没有走索引,则会使用Gap锁锁住当前记录的Gap,防止幻读的发生

4.2 InnoDB中非阻塞读(快照读)底层是怎么实现的?

记录中存储的隐藏列DB_TRX_ID、DB_ROW_ID、DB_ROLL_ID

undo日志根据上述隐藏列来进行记录数据回滚(版本回滚)

review机制

参考文献:

《剑指Java面试-Offer直通车》

《掘金小册——MySQL是怎样运行的》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值