MySQL的间隙锁

Posted Shi Peng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL的间隙锁相关的知识,希望对你有一定的参考价值。

一、、为什么要引入间隙锁

mysql引入间隙锁(Gap Lock),是为了在可重复读事务隔离级别中,解决幻读问题锁引入的锁机制。

二、幻读

2.1、什么是幻读

幻读是指,当一个事务先后两次查询同一个范围的时候,查到的结果不同,这是因为第二次这个事务查到了其他事务对数据所做的更改。

这里,先后执行了Q1 ~ Q3 3次select *** for update读,读到的结果不同:
1)sessionB由于执行了update t set d=5 where id=0, 导致新增了一条满足d=5条件的记录(0,0,5)
2)由于sessionC插入了一条记录,导致新增了一条满足d=5条件的记录(1,1,5)

在可重复读级别下,查询操作都是“快照读”,是不会看到其他事务新插入的记录的。因此,幻读在“当前读”的情况下才会出现。

2.2、间隙锁导致的死锁


1)session A 执行 select … for update 语句,由于 id=9 这一行并不存在,因此会加上 间隙锁 (5,10)
2)session B 执行 select … for update 语句,同样会加上间隙锁 (5,10),间隙锁之间不会冲突,因此这个语句可以执行成功
3)session B 试图插入一行 (9,9,9),被 session A 的间隙锁挡住了,只好进入等待;
4)session A 试图插入一行 (9,9,9),被 session B 的间隙锁挡住了

此时,会产生死锁。InnoDB 的死锁检测马上就发 现了这对死锁关系,让 session A 的 insert 语句报错返回了。
间隙锁的引入,可能会导致同样的语句锁住更大的范围,这其实是影响了性能的。

三、什么是间隙锁

间隙锁(Gap Lock):
1)间隙锁锁定的是索引BTree+叶子节点的next指针
2)间隙锁主要用于解决可重复读事务隔离级别中的幻读问题。

快照读:
在可重复读事务隔离级别下,快照读读到的是数据的当前版本或历史版本。所以快照读无需加锁也可以防止幻读。

当前读:
select…lock in share mode,select…for update
update,delete,insert
当前读:读取的是记录的最新版本,所以就需要通过加锁(行锁、间隙锁、表锁)的方式,使得被当前读读过的数据不能被新增修改或者删除,换句话说再来一次当前读要返回相同的数据

四、间隙锁的使用场景

数据库表:

CREATE TABLE `z` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `b` int(11) DEFAULT NULL,
  `c` int(255) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `b` (`b`)
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8;

b字段是索引,id是主键

INSERT INTO `study`.`z` (`id`, `b`, `c`) VALUES ('1', '1', '0');
INSERT INTO `study`.`z` (`id`, `b`, `c`) VALUES ('3', '6', '1');
INSERT INTO `study`.`z` (`id`, `b`, `c`) VALUES ('5', '4', '2');
INSERT INTO `study`.`z` (`id`, `b`, `c`) VALUES ('7', '8', '3');
INSERT INTO `study`.`z` (`id`, `b`, `c`) VALUES ('8', '10', '4');

BTree+的索引:

锁加在哪里呢?
begin; select * from z where b = 6 for update;

这条sql语句之后看看我们 需要做什么才能保证不发生幻读。
1)不能插入b为6的数据
2)不能删除b为6的数据
3)不能修改b为6的数据
4)不能把别的数据修改为b为6

突然一看挺复杂的,这个锁要怎么加呢,mysql大牛灵机一动,给叶子节点5的next指针加锁,给叶子节点3加行锁,给叶子节点3的next指针加锁。如下图所示


两个next指针锁解决了插入b为6或者把别的数据修改为b为6,行锁解决了修改b为6的行
这样加锁虽然解决了上述问题,但是也带来了副作用:
例如
INSERT INTO study.z (id, b, c) VALUES (‘6’, ‘4’, ‘0’);
执行上述语句会被block, 因为按照索引结构这条数据会插入到叶子结点5和3之间,会修改叶子节点5的next指针,虽然这条sql没有破坏上述的4个红色条件但是依然被阻塞了

INSERT INTO study.z (id, b, c) VALUES (‘4’, ‘4’, ‘0’);
这条语句可插入成功因为这条数据会插入在1的后面5的前面。

五、间隙锁的范围

begin;
select * from z where id=4 for update;
会锁住主键索引叶子节点的3的next指针

begin;
select * from z where id=3 for update;
间隙锁会退化为行锁只锁叶子节点3 ,为什么因为没必要。不加间隙锁也不会打破上述的红色4个条件

begin;
select * from z where id>4 for update;
叶子节点3及之后所有节点会加行锁并且他们的next指针会加锁

begin;
select * from z where c=2 for update;
会发生锁表,因为c没有索引结构能存储行锁或者间隙锁。

以上是关于MySQL的间隙锁的主要内容,如果未能解决你的问题,请参考以下文章

mysql间隙锁 转

深入理解MySQL的间隙锁

MySQL间隙锁(幻读解决原理)

MySQL里的间隙锁以及加锁规则

mysql 间隙锁和临键锁原理

Mysql间隙锁