Mysql:RR隔离级别下的幻读

Mysql:RR隔离级别下的幻读,第1张

众所周知,Mysql在InnoDB下有四种隔离级别

未提交读(Read Uncommitted)

提交后读(Read Committed)

可重复读(Repeatable Read)

串行化(Serializable)

其中可重复读(RR)可以避免脏读( a事务读到b事务回滚前的数据)以及可不重复读( a事务在b事务修改提交的前后,两次分别读到的数据不一致)。但是对于幻读(a事务在b事务insert提交前后,两次分别读到的数据不一致),却存在争议。

下面我们来做一个试验:

对于下面这张简单的数据表

id        num

1         11

2         22

3         33

我们开启a、b两个事务

a事务                b事务

begin                begin

select * from tb    ----

----                    insert into tb (id,num)values(4,44)

----                    commit

select * from tb    ----

commit

试验结果:a事务的两次select查询到的结果相同,在后一次查询中没有返回新插入id=4的那条记录。

据此,很多人判断说RR隔离级别下“不存在”幻读。

但果真如此吗?----

出现上面的试验结果,是因为在RR隔离级别事务下,Mysql会对前一次select的结果快照。所以第二次select其实是快照读(这也正是RR隔离级别下能够避免不可重复读的策略)。

如果我们把试验条件稍作修改,同样开启a、b两个事务:

a事务                b事务

begin                begin

select * from tb    ----

----                    insert into tb (id,num)values(5,55)

----                    commit

update tb set num=num+1    ----        #此处a事务做一次修改 *** 作

select * from tb    ----

commit

试验结果:在a事务的第二次select中出现了b事务新插入的id=5的记录。

由于做了update *** 作,之前的快照失效了,所以说RR隔离级别下的快照策略并没能真正避免幻读。

ps. 假如给第二次的select查询上锁(无论是共享锁还是排它锁),也会得到同样的结果,都会令快照失效。

首先需要明确的就是“幻读”概念: 隔离级别是可重复读,在一个事务中前后两次查询,查到了其他事务insert进来的数据。

强调的是读取到了其他事务插入进来的数据。

下面来论证一下可重复读下幻读的解决方案

先明确一下,for update语法就是当前读,也就是查询当前已经提交的数据,并且是带悲观锁的。没有for update就是快照读,也就是根据readView读取的undolog中的数据。

如果按照以上猜想,那么整个执行结果就违背了 可重复读 的隔离级别了。

那么我们再假设select * from TABLE where d = 5 for update这条语句锁定的是所有被扫描到的数据。

这是因为T2阶段的update会被阻塞住,毕竟所有被扫描到的记录都被锁定了。

按照上述推理过程,很显然,即使锁定所有扫描到的数据行,也依然存在幻读的情况。违背了 可重复读 的隔离级别。

针对这个情况,我们要解决幻读的问题,那么就要求针对所有被扫描的记录行以及还不存在的d=5的记录行都给锁住。

至此,当前查询结果完全满足 可重复读 的隔离级别。

通过以上推论,我们可以总结一下,在可重复读的隔离级别下,解决幻读除了需要锁定所有扫描到的记录行外,还需要锁定行之间的间隙,也就是通过间隙锁来解决幻读的问题。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/6103641.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-03-14
下一篇 2023-03-14

发表评论

登录后才能评论

评论列表(0条)

保存