间隙锁
间隙锁是对索引记录之间的间隙的锁,或者是对第一个索引记录之前或最后一个索引记录之后的间隙的锁。例如,SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;阻止其他事务将 的值插入15到列中t.c1,无论列 中是否已经存在任何此类值,因为该范围内所有现有值之间的间隙被锁定。
间隙锁的目的
是为了防止幻读,其主要通过两个方面实现这个目的:
(1)防止间隙内有新数据被插入
(2)防止已存在的数据,更新成间隙内的数据
间隙是怎么划分的
id(主键) | name | age(普通索引) |
---|---|---|
1 | name1 | 15 |
5 | lucy | 18 |
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(主键) | name | age(普通索引) |
---|---|---|
1 | name1 | 15 |
5 | lucy | 18 |
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的记录加记录锁。