隔离级别RR、间隙锁、幻读

表结构和数据,其中字段c上有一个普通索引。

如下场景:

在事务A中,T1时刻读取到的数据为id=10,15,20三条数据,在T4时刻读取到的数据为id=10,12,15,20,两次读取到的数据条数不一样了。这个就是幻读现象。

innodb中RR隔离级别下的幻读

还是刚刚的场景,但是是在RR隔离级别下来执行:

在事务A中,T1时刻读取到的数据为id=10,15,20三条数据,在T4时刻读取到的数据为id=10,15,20,两次读取到的内容是一样的。

之所以一样,是因为RR隔离级别下有MVCC机制,在使用begin开启事务的时候,执行第一条sql的时候,创建了一个一致性读视图(read-view),后续事务内的读取都是基于这个一致性视图读的,所以看不到其他事务的更改。

再看下面的场景:

会发现,在事务A中,T1时刻读取到的数据为id=10,15,20三条数据,在T4时刻读取到的数据为id=10,12,15,20,两次读取到的数据条数不一样了。

这是因为在mysql中,所以的不加锁的读都是走一致性读,而对于所有的加锁读都是当前读。当前读不是基于当前事务的一致性视图的读取,而是读取的最新已提交的数据。

所以,对于innodb来说,隔离级别为RR的时候,当前读才会产生幻读现象。

幻读的问题

如下场景:

从事务A的角度,在T2时刻查询c=10,只有id=10这一行数据,于是更改这行数据,设置d=0,但实际上最后id=10、12两行数据的d都是0,从语义上,容易造成迷惑。

除了这个问题,幻读更大的问题是数据不一致问题。

如下场景,binlog格式=statement:

执行完后,主库数据:

binlog

insert into t (id,c,d) values(12,12,12)
update t set d=0 where c>=10 and c<15

会发现,binlog中记录的sql顺序和实际主库中执行sql的顺序是有区别的,当从库重放binlog的时候,

就会出现主从数据不一致,id=12这条数据在主库和从库数据是不一样的。如果mysql由于某种原因需要根据binlog进行数据恢复,恢复出来的数据也是和开始是不一致的。

ps:所以说,如果使用mysql的隔离界别是CR,那么binlog的格式一定要用ROW。

 

RR隔离级别解决幻读问题--间隙锁

幻读问题是指事务内相同的两次读取却读取到了不同的数据,innodb解决的思路就是加锁阻塞。

同样是如上场景:

事务A在T2执行的时候,会对id=10这一行加上行锁,并且对c=(5,10),(10,15)这个间隙肩上锁,当事务B在T3执行插入的时候,由于间隙锁冲突,会阻塞等待,等事务A提交后事务B才能继续执行。

这个时候,binlog记录的语句就和主库实际执行的顺序是一样的:
 

update t set d=0 where c>=10 and c<15

insert into t (id,c,d) values(12,12,12)

这样看起来是不是在隔离级别RR下”完美“解决了幻读问题了?

看如下场景:

在T2时刻读取如下:

在T5时刻读取如下:

相同条件的两次读取,读取到了不同的数据条数,这种情况就只能串行隔离级别能解决了。

所以说,在隔离界别下,解决了部分场景的幻读问题,保证了数据的正确性,但是因为事务内的读取要看到本事务的修改,还有一些场景的幻读是解决不了的。总结一下:

  1. 在RR级别下,由于有MVCC,基于当前事务的一致性读视图的读取,是看不到别的事务的更改的(别的事务更改了,本事无又更改了,是可见的),一定程度上解决了一些场景的幻读问题。ps:也正是mvcc,实现了一致性读的免锁读。
  2. 引入间隙锁,保证了binlog格式=statement的情况下,binlog也是正确的。由于间隙锁的存在,阻塞了在间隙插入数据,对于当前读的幻读问题,间隙锁是可以保证没有幻读的。

另外,为什么说间隙锁不是为了解决幻读的一种机制,不是只是对insert才有效,对update是一样的:

这一样会被阻塞,虽然对于数据来说,事务B不是在插入数据,但是对于索引c来说,就是在(10,15)这个间隙中插入了一个数据,因为这个间隙中上了间隙锁,会阻塞。

间隙锁

innodb的数据是按照索引来组织的,数据是放在主键索引树上的:

如果还有其他辅助索引,比如在字段c上加了一个索引:会有另外一个B+树,叶子节点存的是主键。

那么我们说的间隙锁,到底是谁的间隙?比如入下语句,加锁情况到底是怎样的:

update t set d=0 where c=10;

先给结论:

  1. 间隙是加载索引上的,使用到哪个索引就加在哪个索引上。
  2. 对于修改类操作,一定会在主键索引树上加行锁。

所以,加锁示意图:

可以验证下:

初始数据如下:

执行如下sql:

T3事务B不会阻塞,执行成功,原因就是间隙锁是加在了索引c上,没有在主键上加间隙。

如果事务B执行的是:insert into  t (id,c,d) values(14,10,10),就会被阻塞。其实只要这里c列的 值在(10,15)之间,都会被阻塞。

所以说,间隙是指用到那个索引列,就是该列上的索引。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值