幻读

现在有一个表,初始化如下:

现在有一条语句select * from t where d=5 for update,因为在d上没有索引,所以它需要扫描整个表,那么,其他扫描到的d不是5的行,需要加锁吗?

看以下场景:

幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。

1. 在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。因此,幻读在当前读下才会出现。

2. 上面session B的修改结果,被session A之后的select语句用当前读看到,不能称为幻读。幻读仅专指新插入的行

幻读的影响

session A在T1时刻就声明了,“我要把所有d=5的行锁住,不准别的事务进行读写操作”。而实际上,这个语义被破坏了。更甚,我们看看以下语句:

session AT1时刻再加一个更新语句,即:update t set d=100 where d=5。看看它的执行结果:

1. 经过T1时刻,id=5这一行变成 (5,5,100),当然这个结果最终是在T6时刻正式提交的;

2. 经过T2时刻,id=0这一行变成(0,5,5);

3. 经过T4时刻,表里面多了一行(1,5,5);

4. 其他行跟这个执行序列无关,保持不变。

这样看,这些数据也没啥问题,但是我们再来看看这时候binlog里面的内容。

1. T2时刻,session B事务提交,写入了两条语句;

2. T4时刻,session C事务提交,写入了两条语句;

3. T6时刻,session A事务提交,写入了update t set d=100 where d=5 这条语句。

那放在一起的话,binlog里面就是这样的:

这样就会出现了数据不一致的问题了。

那这样看来,应该把扫描过的行,都加上锁,如下:

由于session A把所有的行都加了写锁,所以session B在执行第一个update语句的时候就被锁住了。需要等到T6时刻session A提交以后,session B才能继续执行。这样对于id=0这一行,在数据库里的最终结果还是 (0,5,5)。在binlog里面,执行序列是这样的:

Id为0的这行,问题解决了,但是session C中新增的id = 1这一行,却被改了。

原因很简单。在T3时刻,我们给所有行加锁的时候,id=1这一行还不存在,不存在也就加不上锁。也就是说,即使把所有的记录都加上锁,还是阻止不了新插入的记录,这也是为什么幻读会被单独拿出来解决的原因。

 

解决幻读

间隙锁:就是两个值之间的空隙加上锁,比如初始化中插入了6个记录,那么就会在7个空隙中都加上锁,这样,就没办法加入新的记录了。间隙锁是在可重复读隔离级别下才会生效的。

需要注意的是,跟间隙锁存在冲突关系的,是往这个间隙中插入一个记录这个操作。间隙锁之间都不存在冲突关系。

这里session B并不会被堵住。因为表t里并没有c=7这个记录,因此session A加的是间隙锁(5,10)。而session B也是在这个间隙加的间隙锁。它们有共同的目标,即:保护这个间隙,不允许插入值。但,它们之间是不冲突的。

间隙锁可能会导致死锁

1. session A 执行select … for update语句,由于id=9这一行并不存在,因此会加上间隙锁(5,10);

2. session B 执行select … for update语句,同样会加上间隙锁(5,10),间隙锁之间不会冲突,因此这个语句可以执行成功;

3. session B 试图插入一行(9,9,9),被session A的间隙锁挡住了,只好进入等待;

4. session A试图插入一行(9,9,9),被session B的间隙锁挡住了。

当然,InnoDB的死锁检测马上就发现了这对死锁关系,让session Ainsert语句报错返回了

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值