什么是幻读?
幻读是指在同一个事务中,存在前后两次查询同一个范围的数据,但是第二次查询却看到了第一次查询没看到的行。
幻读出现的场景
在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。因此,幻读在“当前读”下才会出现。
幻读仅专指“新插入的行”,不是“修改后的行”。
幻读带来的问题
- 对行锁语义的破坏
- 破坏了数据和日志在逻辑上的一致性
怎么解决幻读?
产生幻读的原因是,行锁只能锁住行,但是新插入记录这个动作,要更新的是记录之间的“间隙”。因此,为了解决幻读问题,InnoDB 只好引入新的锁,也就是间隙锁 (Gap Lock)。
跟间隙锁存在冲突关系的,是“往这个间隙中插入一个记录”这个操作。间隙锁之间都不存在冲突关系。
间隙锁和行锁合称 next-key lock,每个 next-key lock 是前开后闭区间。
间隙锁带来的问题
间隙锁和 next-key lock 的引入,解决了幻读的问题,但同时也带来了一些问题
例如这个间隙锁导致的死锁:
- session A 执行 select … for update 语句,由于 id=9 这一行并不存在,因此会加上间隙锁 (5,10);
- session B 执行 select … for update 语句,同样会加上间隙锁 (5,10),间隙锁之间不会冲突,因此这个语句可以执行成功;
- session B 试图插入一行 (9,9,9),被 session A 的间隙锁挡住了,只好进入等待;
- session A 试图插入一行 (9,9,9),被 session B 的间隙锁挡住了。两个 session 进入互相等待状态,形成死锁。
间隙锁的引入,可能会导致同样的语句锁住更大的范围,影响了并发度。
如何避免间隙锁带来的问题
间隙锁是在可重复读隔离级别下才会生效的。所以,如果把隔离级别设置为读提交的话,就没有间隙锁了。但同时,要解决可能出现的数据和日志不一致问题,需要把 binlog 格式设置为 row。