MySQL间隙锁

1,398 阅读4分钟

间隙锁锁的是若干个索引间的间隙,每个间隙都是两端开放的区间,MySQL InnoDB支持三种行锁定方式:

  • 行锁(Record Lock):锁直接加在索引记录上面,锁住的是key。
  • 间隙锁(Gap Lock):锁定索引记录间隙,确保索引记录的间隙不变。间隙锁是针对事务隔离级别为可重复读或以上级别而已的。锁定一个范围,但不包含记录本身
  • Next-Key Lock :行锁和间隙锁组合起来就叫Next-Key Lock。 锁定一个范围,并且锁定记录本身

innodb自动使用间隙锁的条件:

  1. 必须在RR级别下
  2. 检索条件必须有索引(没有索引的话,mysql会全表扫描,那样会锁定整张表所有的记录,包括不存在的记录,此时其他事务不能修改不能删除不能添加)

间隙锁的目的是为了防止幻读,其主要通过两个方面实现这个目的:

  1. 防止间隙内有新数据被插入
  2. 防止已存在的数据,更新成间隙内的数据

下面通过例子来说明一下

新建一张表,这张表除了主键没有建其他索引

CREATE TABLE `hero` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(10) NOT NULL DEFAULT '' COMMENT 'utf8',
  `age` int(11) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

插入数据,如图:

我们在A客户端执行如下语句:

BEGIN;
SELECT * FROM hero WHERE age = 3 FOR UPDATE;

然后我们在B客户端对id=1的数据进行修改:

UPDATE hero SET name='卢俊义' WHERE id = 1;

发现锁住了:

这是因为age不是索引,A客户端执行后,hero表变为表锁了。

上面只是说明一种行锁变表锁的一种情况,具体可以看这篇文章

既然要看间隙锁,那我们要对age加个索引了

CREATE TABLE `hero` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(10) NOT NULL DEFAULT '' COMMENT 'utf8',
  `age` int(11) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `idex_age` (`age`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;

数据和图一一样,没有变

在A客户端执行如下语句:

BEGIN;
SELECT * FROM hero WHERE age = 3 FOR UPDATE;

然后我们在B客户端对id=1的数据进行修改:

UPDATE hero SET name='卢俊义' WHERE id = 1;

发现修改成功了

我们在C客户端对id=1的数据进行修改:

UPDATE hero SET age=3 WHERE id = 1;

结果锁住了,如下:

我们在D客户端新增一条数据:

INSERT INTO hero value(9,'吴用',3)

发现也是锁住的:

SELECT * FROM hero WHERE age = 3 FOR UPDATE;这句会对age=3的向左取得最靠近的值2作为左区间,向右取得最靠近的4作为右区间。这里我们不能靠图一这种数据来判断区间,而是应该根据非聚集索引,因为age是辅助索引,它对应一颗B+树

的非聚集索引,我们都知道B+树是有顺序的数据结构,直白来说,我们的例子,age在B+树上是1-2-3-4-5-6-7这种排序的,现在是3这个节点左边是2右边是4(2-3-4),也就是在2-3-4这串数据之间不能在插入数据了,所以INSERT INTO hero value(9,'吴用',3)这句,插入的age=3,这样肯定会改变2-3-4的结构(变成2-3-3-4,中间插入3了)这就是间隙锁,而不能看id的顺序认为9不在age2,3,4数据之间,就认为可以插入成功。

对于INSERT INTO hero value(9,'吴用',4)这句是会成功的,因为他会变成2-3-4-4,第二个4是插入的,他并没有在2-3-4直接插入,所以是成功的。

INSERT INTO hero value(9,'吴用',2)是阻塞的。因为他会变成2-2-3-4,第二个2是新插入的。

同理UPDATE hero SET age=3 WHERE id = 1;这句也会阻塞,因为他也会改变现在的2-3-4结构(变成2-3-3-4,中间插入3了)

UPDATE hero SET age=4 WHERE id = 2;这句也会阻塞,但是UPDATE hero SET age=4 WHERE id = 5;这句成功,这里大家会觉得奇怪,我们看图:

我认为当age相同的时候,B+树会根据id小的在前面大的在后面排序,UPDATE hero SET age=4 WHERE id = 2之后,我们看到2-3-4已经没有了,所以这种情况会阻塞

我们看看UPDATE hero SET age=4 WHERE id = 5这种情况:

我们可以发现更新成功后,并没有破环2-3-4结构,所以这种情况是成功的

所以间隙锁的区间是看B+树索引那边的顺序,而不是看select出来的顺序在上面认为可以插入。

参考:

www.ius7.com/a/351

z.itpub.net/article/det…

www.jianshu.com/p/bf862c37c…

blog.csdn.net/tr1912/arti…

www.cnblogs.com/aspirant/p/…

www.jb51.net/article/159…

www.cnblogs.com/JMrLi/p/127…