间隙锁是一个在索引记录之间的间隙上的锁。
其中间隙锁的使用就是为了保证某一个间隙内的数据在锁定情况下不发生任何的变化,例如MySQL的默认隔离级别为可重复读(RR),则其使用间隙锁的目的即是为了防止幻读。
我们假设有下面的场景:id作为主键,number字段上有一个非唯一索引的二级索引,那么此时哪些场景不能再插入number = 5的记录??
答:只有我们保证number之前,number = 5现有记录之间,以及number之后不能插入新的记录,则新的number = 5的记录就不能插入进来!!
那么MySQL底层是怎么做到的,即使用到了我们的间隙锁!!!
此时我们锁住了(4,5),(5,5),(5,11)之间的间隙,则number = 5的记录就无法在插入进去了。。。
我们根据检索条件(number = 5)向左寻找最靠近检索条件的记录值A,作为左区间,向右寻找最靠近检索条件的记录值B作为右区间,即锁定的间隙为(A,B)。
在上面的场景中,检索条件where number = 5的话,那么间隙锁的区间范围为(4,11),也就是我上面在右侧画出来的几部分之和。
间隙锁的目的是为了防止幻读,其主要通过两个方面实现这个目的:
(1)必须在RR级别下
(2)检索条件必须有索引(没有索引的话,mysql会全表扫描,那样会锁定整张表所有的记录,包括不存在的记录,此时其他事务不能修改不能删除不能添加)
对于上面的案例,我们观察一下几个案例,来熟悉一下间隙锁的使用:
案例1:
session 1:
start transaction ;
select * from news where number = 4 for update ;
session 2:
start transaction ;
insert into news value(2,4);#(阻塞)
insert into news value(2,2);#(阻塞)
insert into news value(4,4);#(阻塞)
insert into news value(4,5);#(阻塞)
insert into news value(7,5);#(执行成功)
insert into news value(9,5);#(执行成功)
insert into news value(11,5);#(执行成功)
此时我们查询的是number = 4的条件,此时它会锁定区域【2,4】以及【4,5】,所以此时前三条对于该间隙中的记录的插入操作均阻塞,必须等到select for update事务提交以后才会执行,而后面的几条不在该间隙锁的间隙范围内的操作均可以成功。
案例2:
session 1:
start transaction;
select * from news where number>4 for update;
session 2:
start transaction;
update news set id=2 where number=4 ;#(执行成功)
update news set id=4 where number=4 ;#(阻塞)
update news set id=5 where number=5 ;#(阻塞)
insert into news value(2,3);#(执行成功)
insert into news value(null,13);#(阻塞)
此时检索条件为范围查询:number > 4,故我们选择向左找到了最靠近4的值作为左区间,向右取无穷大作为右值,即我们的索引的范围【4,无穷大】
那么对于事务2来说:当我们的number在此间隙区间内的,就会阻塞住,防止插入新的记录导致幻读的发生。
故我们不可能重复插入相同的索引对应的记录,则我们对其只会索引当前行,即行锁,不会使用间隙锁。