我们今天来谈谈幻读
在MySQL当中特别是InnoDB引擎下,如果select后加了locked in share mode,或者update就是当前读,否则就是一致性读。
我们在当前读的情况下进行下面情况的模拟,
我们插入数据(0,0,0)~(5,5,5)他们对应的是(id,c,d)
我们开三个事务ABC,他们的执行顺序是这样的:
1. A事务读 where d=5
2. B事务update t set d=5 where id=0
3.A事务读where d=5
3. C事务insert into t valuse(1,1,5)
4.A事务读where d=5
在一个事务当中因为其他事务对数据的更改导致后一次查询相同对象的时候,后一次看到了前一次没有看到的行,称之为幻读。
那么我们如果给A事务读的过程中给这一行加上锁呢?即为where d =5 for update。
这样执行下来,我们会发现出了很大的问题,A事务在执行的过程中就是声明了给所有d=5的这一行加上了锁,但是我们在B事务当中进行更改之后也出现了d=5的这一行(C事务也进行了更改,与B事务相同),出现了数据的更改,数据一致性不同,并且A事务的语义也被破坏了,这就是问题所在的地方。
我们如果给所有扫到的数据加上锁会怎么样呢?我们来分析看看,为了方便分析,我们给多加点步骤
1. A事务读 where d=5,update t set d=100 where d=5
2. B事务update t set d=5 where id=0,update t set c=5 where id =0
3.A事务读where d=5
3. C事务insert into t valuse(1,1,5),update t set c=5 where id =1
4.A事务读where d=5
5.commit;
我们来看看是如何执行的,首先时刻1的时候给所有d=5都加上了锁,接着到了b事务,因为扫到的所有行都加上了锁,所以得等锁释放了才能执行,即为在5时刻等A事务commit之后,但是C事务当中的添加并不是原有的所以并不会被加上锁,执行过程是这样的。
首先C事务将数据加入,然后对这个刚加入的数据进行修改工作,因为这行是没有加锁的,然后事务A提交,释放锁,开始执行,将所以的d=5的行改为100,然后进行B事务的操作。
我们可以看到,就算给所以的行加上锁了,也阻止不了新增加的数据,那么幻读到底该怎么处理呢?
产生幻读的原因是,行锁只能锁住行,但是新插入数据的这个操作是进行行的更新,这是不加锁的,所以为了解决这种问题,InnoDB引入了间隙锁的概念,所谓间隙锁就是在数据直接加锁,防止你插入新数据导致幻读的产生。
但是这种间隙锁的引入,也带来了一些问题,就比如说触发死锁,(问题大概是:查询数据,数据不存在时进行更新插入)当A事务对一个不存在的数据进行查询的时候会加入间隙锁,然后B查询一个不存在的数据,也会触发间隙锁,但是如果A和B这个时候要进行数据的插入,A会被B的间隙锁挡住进入等待,B也会被A的间隙锁挡住,进入等待,最终成为死锁。
我们可以发现,间隙锁的引入,可能会导致同样的语句锁住更大的范围,这其实是影响了并发度的。
这些问题的分析下都是在可重复读的隔离级别下实现的,也就是说间隙锁是在可重复读的隔离级别下才会生效的,所以我们要把隔离级别设置为读提交,为了解决数据和日志不一致的问题,我们得将binlog的格式设置为row。
将binlog的格式设置为row可以: