2
头图

间隙锁

间隙锁是对索引记录之间的间隙的锁,或者是对第一个索引记录之前或最后一个索引记录之后的间隙的锁。例如,SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;阻止其他事务将 的值插入15到列中t.c1,无论列 中是否已经存在任何此类值,因为该范围内所有现有值之间的间隙被锁定。

间隙锁的目的

是为了防止幻读,其主要通过两个方面实现这个目的:
(1)防止间隙内有新数据被插入
(2)防止已存在的数据,更新成间隙内的数据

间隙是怎么划分的

id(主键)nameage(普通索引)
1name115
5lucy18
11南风22
20洛神赋28

这个表根据age列(间隙锁作用在索引上,必须要有索引),间隙可以划分为 (-∞, 15),(15,18),(18,22),(22,28),(28,+∞)

间隙锁锁定的区域

根据检索条件向左寻找最靠近检索条件的记录值A,作为左区间,向右寻找最靠近检索条件的记录值B作为右区间,即锁定的间隙为(A,B)。

间隙锁作用范围

1、间隙锁只能作用在RR隔离级别

2、能作用在索引上

补充:

1、使用唯一索引锁定行以搜索唯一行的语句不需要间隙锁定。(这不包括搜索条件仅包含多列唯一索引的某些列的情况;在这种情况下,确实会发生间隙锁定。)

2、不同的事务可以在间隙上持有冲突的锁。例如,事务 A 可以在间隙上持有共享间隙锁(间隙 S 锁),而事务 B 在同一间隙上持有排他间隙锁(间隙 X 锁)。允许冲突间隙锁的原因是,如果从索引中清除记录,则必须合并不同事务在记录上持有的间隙锁。

3、可以明确禁用间隙锁定。将事务隔离级别更改为READ COMMITTED即可

准备数据:

创建表:

CREATE TABLE `user` (
    `id` INT(10) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(50) NULL DEFAULT '0' COLLATE 'utf8_general_ci',
    `age` INT(10) NULL DEFAULT '0',
    PRIMARY KEY (`id`) USING BTREE,
    INDEX `age` (`age`) USING BTREE
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=11
;

表数据:

id(主键)nameage(普通索引)
1name115
5lucy18
11南风22
20洛神赋28

普通索引的间隙锁

在普通索引列上,不管是何种查询,只要加锁,都会产生间隙锁,这跟唯一索引不一样;
在普通索引跟唯一索引中,数据间隙的分析,数据行是优先根据普通索引排序,再根据唯一索引排序。

案例1(检索单个值)

开启两个会话,设置会话隔离级别为RR,设置自动提交为0,开启事物测试:

##############session 1

SET autocommit=0;
SET session transaction isolation level REPEATABLE READ;

START TRANSACTION;
SELECT * FROM USER WHERE age=22 FOR UPDATE 

############session 2

SET autocommit=0;
SET session transaction isolation level REPEATABLE read;

START TRANSACTION;
INSERT INTO user VALUE(3, 'gap lock', 18) #成功
INSERT INTO user VALUE(6, 'gap lock', 18) #阻塞
INSERT INTO user VALUE(3, 'gap lock', 20) #阻塞
INSERT INTO user VALUE(10, 'gap lock', 22) #阻塞
INSERT INTO user VALUE(14, 'gap lock', 28) #阻塞
INSERT INTO user VALUE(21, 'gap lock', 28) #成功

会话1执行sql: age=22 会锁定间隙[15,18)(18,22), 会话2中 age=[15,22)之间的插入都会失败。2、同样插入age=22,id=10的时候可以成功,id=14的时候就会失败,这说明当索引顺序相同时,会根据主键来排序。对age=18同理

锁定区域示意图:

案例2 (检索不存在的值)

仍基于原始表数据测试验证:

##############session 1

SET autocommit=0;
SET session transaction isolation level REPEATABLE READ;

START TRANSACTION;
SELECT * FROM USER WHERE age=30 FOR UPDATE 

############session 2

SET autocommit=0;
SET session transaction isolation level REPEATABLE read;

START TRANSACTION;
INSERT INTO user VALUE(13, 'gap lock', 28) #阻塞
INSERT INTO user VALUE(14, 'gap lock', 100) #阻塞
INSERT INTO user VALUE(15, 'gap lock', 27) #成功

*会话1执行sql:条件 age=30
会锁定间隙[28,正无穷大), 会话2中 age>=28 的插入都会失败*

案例3 (检索范围)

仍基于原始表数据测试验证:

##############session 1

SET autocommit=0;
SET session transaction isolation level REPEATABLE READ;

START TRANSACTION;
SELECT * FROM USER WHERE age>=18 AND age<23 FOR UPDATE 

############session 2

SET autocommit=0;
SET session transaction isolation level REPEATABLE read;

START TRANSACTION;
INSERT INTO user VALUE(11, 'select range', 14) #成功
INSERT INTO user VALUE(11, 'select range', 15) #阻塞
INSERT INTO user VALUE(12, 'select range', 17) #阻塞
INSERT INTO user VALUE(13, 'select range', 18) #阻塞
INSERT INTO user VALUE(14, 'select range', 20) #阻塞
INSERT INTO user VALUE(17, 'select range', 23) #阻塞
INSERT INTO user VALUE(18, 'select range', 24) #阻塞
INSERT INTO user VALUE(19, 'select range', 28) #成功

*会话1执行sql:条件 age>=18 AND age<23
会锁定age=[15,28)之间的间隙区间, 会话2中 age>=15 and age<28 的插入都会失败。 由于23是22-28之间不存在的记录,所以这个间隙区间也被锁定了*

next-key锁

next-key锁是记录锁和间隙锁的组合,mysql默认使用这个锁。
上面的案例一session 1中的sql是:SELECT * FROM USER WHERE age=18 FOR UPDATE; next-key锁锁定的范围为间隙锁+记录锁,首先在(15,18)(18,22)加间隙锁,同时age=18的记录加记录锁。


杜若
70 声望3 粉丝