幻读
-
幻读在「当前读」下才会出现
-
幻读仅专指「新插入的行」
什么是幻读?
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c` (`c`)
) ENGINE=InnoDB;
insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);

其中,Q3读到id=1这一行的现象,称为「幻读」
一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行
在事务可见性层面并无问题
幻读有什么问题?
-
语义上,sessionA在T1时刻声明「锁住d=5的所有行,不准其它读写操作」。而这个语义被破坏了
-
数据一致性问题

由于sessionA把所有的行都加了写锁, 所以sessionB在执行第一个update语句的时候就被锁住了。
需要等到T6时刻,sessionA提交以后,sessionB才能继续执行
所以,上面session B的修改结果,被session A之后的select语句用“当前读”看到,不能称为幻读。
它并不会引发数据一致性问题
binlog执行序列:
insert into t values(1,1,5); /*(1,1,5)*/
update t set c=5 where id=1; /*(1,5,5)*/
update t set d=100 where d=5;/*所有d=5的行,d改成100*/
update t set d=5 where id=0; /*(0,0,5)*/
update t set c=5 where id=0; /*(0,5,5)*/
这个语句序列,拿到备库去执行,或用binlog来克隆一个库:
(5,5,100)、(1,5,100)、 (0,5,5)
id=1这一行,在原数据库里面的结果是(1,5,5),而根据binlog的执行结果是(1,5,100)
新插入的行并不存在,加不了行锁
如何解决幻读?
产生幻读的原因:行锁只能锁住行,但是新插入记录这个动作,要更新的是记录之间的「间隙」。
因此,为了解决幻读问题,InnoDB引入了「间隙锁」(Gap Lock)
-
间隙锁,锁的就是两个值之间的空隙。比如文章开头的表t,初始化插入了6个记录,这就产生了7个间隙。

-
间隙锁和行锁合称
next-key lock,Next-key lock前开后闭
如果用select * from t for update要把整个表所有记录锁起来,就形成了7个next-key lock,分别是
(-∞,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, +suprenum]
-
suprenum
这是因为+∞是开区间。实现上,InnoDB给每个索引加了一个不存在的最大值suprenum,这样才符合我们前面说的“都是前开后闭区间”
-
间隙锁记为开区间
间隙锁引入的性能问题
举例业务逻辑:任意锁住一行,如果这一行不存在插入,存在则更新:
begin;
select * from t where id=N for update;
/*如果行不存在*/
insert into t values(N,N,N);
/*如果行存在*/
update t set d=N set id=N;
commit;
问题:一旦有并发,就会碰到死锁

语句分析:
-
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进入互相等待状态,形成死锁。当然,InnoDB的死锁检测马上就发现了这对死锁关系,让session A的insert语句报错返回了。
读提交隔离级别加binlog_format=row的组合
-
为了解决幻读的问题,我们引入了这么一大串内容,有没有更简单一点的处理方法呢
间隙锁是在可重复读隔离级别下才会生效的
所以,你如果把隔离级别设置为读提交的话,就没有间隙锁了。但同时,你要解决可能出现的数据和日志不一致问题,需要把binlog格式设置为row。这,也是现在不少公司使用的配置组合。

浙公网安备 33010602011771号