前面一文 mysql锁 介绍了mysql innodb存储引擎的各类锁,本文介绍一下innodb存储引擎的间隙锁,就如下问题展开讨论mysql
1.什么是间隙锁?间隙锁是怎样产生的?sql
2.间隙锁有什么做用?性能
3.使用间隙锁有什么隐患?spa
当咱们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但不存在的记录,叫作“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(NEXT-KEY)锁。.net
上面的文字很抽象,如今举个栗子,介绍间隙锁是怎么产生的:blog
假设有如下表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;ci
这时候,咱们命中的数据为如下着色部分:get
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为一、3和4的这3条数据。
可是,看看前面咱们的介绍——对于键值在条件范围内但不存在的记录,叫作“间隙(GAP)”,InnoDB也会对这个“间隙”加锁。
好了,咱们这里,键值在条件范围可是不存在的记录,就是id为2的记录,这里会对id为2数据加上间隙锁。假设这时候若是有id=2的记录insert进来了,是要等到这个事务结束之后才会执行的
总的来讲,有2个做用:防止幻读和防止数据误删/改
关于幻读的概念能够参考我这篇文章 https://blog.csdn.net/mweibiao/article/details/80805031 ,这里就很少作解释了
假设有下面场景
时间 | 事务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读到的结果是不同的,有了间隙锁,读的就是同样的了
这个做用比较重要,假设如下场景:
时间 | 事务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; |
这种状况,对插入的性能就有很大影响了,必须等到事务结束才能进行插入,性能大打折扣
更有甚者,若是间隙锁出现死锁的状况下,会更隐晦,更难定位