间隙锁
简介
间隙锁是对数据库表一定范围上的加锁,它能和行锁组成next-key lock解决在可重复读事务隔离级别下产生的幻读问题。幻读就是一个事务在进行范围查询的时候,前后查询的结果不一样。
原则
来记录下MySQL在可重复读事务隔离级别下加锁的规则:
- 加锁的基本单位是next-key lock,它是前开后闭的原则。
- 查询过程中访问的对象会增加锁
- 索引上的等值查询–给唯一索引加锁的时候,next-key lock会升级为行锁
- 索引上的等值查询–向右遍历时,最后一个值不满足查询要求的话,会将next-key lock退化为间隙锁
- 唯一索引在范围查询时会访问到不满足条件的第一个值为止
通过案例解释上面的原则
先声明一下,下面的这些案例都是基于可重复读事务隔离级别的
数据介绍
假设现在我有几条数据:
步骤 | id(唯一索引) | c(普通索引) | d(无索引) |
---|---|---|---|
1 | 1 | 1 | 1 |
2 | 5 | 5 | 5 |
3 | 10 | 10 | 10 |
4 | 15 | 15 | 15 |
以上数据为了解决幻读的问题,更新的时候,不只是对上述的每行数据都加行锁,并且对于中间的取值范围也加了锁,这个锁就是我们所说的间隙锁。这样以来,行锁+间隙锁组成next-key lock,这个锁的信息就是:(-°,1],(1,5],(5,10],(10,15],(15,+supernum],(其中,supernum是MySQL数据库维护的最大的值,来保证next-key lock是左开右闭的原则)
案例
案例1:间隙锁简单案例
步骤 | 事务A | 事务B |
---|---|---|
1 | begin; select * from table where id = 3 for update |
|
2 | - | insert into tabble value(4,4,4) block; |
3 | commit; | - |
这个就是一个简单的案例,我们来分析下他的流程:
- 当有上面这两个事务时,事务A的操作会对数据库增加一个(1,5]范围的锁,这时候事务B是不能进行插入数据的,处于阻塞状态
案例2:间隙锁死锁问题
步骤 | 事务A | 事务B |
---|---|---|
1 | begin; select * from table where id = 3 for update |
|
2 | - | begin; select * from table where id = 4 for update |
3 | - | insert into table value(2,2,2) block; |
3 | nsert into table value(2,2,2) block; |
- |
不同于写锁是互斥的,间隙锁不是互斥的,两个事务都能获取到这个间隙锁,但是获得这个间隙锁之后,是不允许其他事务对这个间隙锁进行DDL操作的,所以两者就进入到死锁状态
案例3:等值查询–唯一索引
步骤 | 事务A | 事务B | 事务C |
---|---|---|---|
1 | begin; update table set d=d+1 where id = 7 |
- | - |
2 | - | insert into table value(8,8,8) block; |
- |
3 | - | - | update table set d=d+1 where id=10 |
1、先来看看MySQL会给数据库生成什么范围的锁:(5,10]
2、我们根据上面的原则四,我们看到这个范围最后一个值10与我们要查的id=7不相等,就会将这个next-key lock退化为间隙锁(5,10);
3、所以就可以解释事务B进行的插入操作是阻塞的,而事务C的操作是不阻塞的了。
案例4:等值查询–普通索引
步骤 | 事务A | 事务B | 事务C |
---|---|---|---|
1 | begin; select id from table where c=5 lock in share mode |
- | - |
2 | - | update table set d=d+1 where id=5 | - |
3 | - | - | insert into table values(7,7,7) block; |
1、先来看看MySQL会给数据库生成什么范围的锁:(0,5],(5,10]
2、由于查询是等值查询,并且最后一个值不满足查询要求,故退化为间隙锁(5,10)
3、事务B能正常执行不阻塞的原因就是,事务A走的是覆盖索引并没有对主键索引加锁,所以B能正常执行
4、事务C插入的信息是7,在(5,10)之间,故会阻塞。