前面一文 mysql锁 介绍了mysql innodb存储引擎的各种锁,本文介绍一下innodb存储引擎的间隙锁,就以下问题展开讨论

1.什么是间隙锁?间隙锁是怎样产生的?

2.间隙锁有什么作用?

3.使用间隙锁有什么隐患?

一、间隙锁的基本概念

1.什么叫间隙锁

当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(NEXT-KEY)锁。

2.间隙锁的产生

上面的文字很抽象,现在举个栗子,介绍间隙锁是怎么产生的:

假设有以下表t_student:(其中id为PK,name为非唯一索引)

id
name
sex
address
1
zhaoyi
0
beijin
3
sunsan
1
shanghai
4
lisi
0
guangzhou
5
zhouwu
0
shenzhen
6
wuliu
1
hangzhou

这个时候我们发出一条这样的加锁sql语句:

select id,name from t_student where id > 0 and id < 5 for update;

这时候,我们命中的数据为以下着色部分:

id
name
sex
address
1
zhaoyi
0
beijin
3
sunsan
1
shanghai
4
lisi
0
guangzhou
5
zhouwu
0
shenzhen
6
wuliu
1
hangzhou

细心的朋友可能就会发现,这里缺少了条id为2的记录,我们的重点就在这里。

select ... for update这条语句,是会对数据记录加锁的,这里因为命中了索引,加的是行锁。从数据记录来看,这里排它锁锁住数据是id为1、3和4的这3条数据。

但是,看看前面我们的介绍——对于键值在条件范围内但不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁。

好了,我们这里,键值在条件范围但是不存在的记录,就是id为2的记录,这里会对id为2数据加上间隙锁。假设这时候如果有id=2的记录insert进来了,是要等到这个事务结束以后才会执行的

二、间隙锁的作用

总的来说,有2个作用:防止幻读和防止数据误删/改

1.防止幻读

假设有下面场景

时间

事务A

事务B

T1
select count(1) from t_student where id > 1;
T2
insert into t_student values(2,'qianer',1,'nanjing');
T3
commit;
T4
select count(1) from t_student where id > 1;
T5
commit;

如果没有间隙锁,事务A在T1和T4读到的结果是不一样的,有了间隙锁,读的就是一样的了

2.防止数据误删/改

这个作用比较重要,假设以下场景:

时间

事务A

事务B

T1
delete from t_student where id < 4;
T2
insert into t_student values(2,'qianer',1,'nanjing');
T3
commit;
T4
commit;

这种情况下,如果没有间隙锁,会出现的问题是:id为2的记录,刚加进去,就被删除了,这种情况有时候对业务,是致命性的打击。加了间隙锁之后,由于insert语句要等待事务A执行完之后释放锁,避免了这种情况

三.使用间隙锁的隐患

最大的隐患就是性能问题

前面提到,假设这时候如果有id=2的记录insert进来了,是要等到这个事务结束以后才会执行的,假设是这种场景

时间

事务A

事务B

T1
select * from t_student where id>1 and id < 100 for update;
T2
insert into t_student values(2,'qianer',1,'nanjing');
T3
update t_student set xxxx where id=xxx;
T4
update t_student set xxxx where id=xxx;
T5
update t_student set xxxx where id=xxx;
T6
…
T7
commit;

这种情况,对插入的性能就有很大影响了,必须等到事务结束才能进行插入,性能大打折扣

更有甚者,如果间隙锁出现死锁的情况下,会更隐晦,更难定位