文章目录
1.InnoDB锁的验证
接上一篇文章,【MySQL】锁机制,这里对里面提到的InnoDB中的锁做验证。
所有的验证都是在RR隔离级别下进行的。
首先创建并初始化test表数据:
CREATE TABLE `test` (
`id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`sn` int(11) DEFAULT NULL,
`uq` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_uq` (`uq`) USING BTREE,
KEY `idx_sn` (`sn`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `test` (`id`, `name`, `sn`, `uq`) VALUES (1, '张三', 3, 3);
INSERT INTO `test` (`id`, `name`, `sn`, `uq`) VALUES (4, '李四', 5, 5);
INSERT INTO `test` (`id`, `name`, `sn`, `uq`) VALUES (7, '王五', 7, 7);
INSERT INTO `test` (`id`, `name`, `sn`, `uq`) VALUES (10, '赵六', 11, 11);
1.1.验证是否对索引加锁
验证没有加索引的字段。
首先用没加索引的name字段做筛选条件,先开启一个session,锁住一行数据。
-- sesison 1
begin;
select * from test where name = '张三' for update;
+----+------+----+----+
| id | name | sn | uq |
+----+------+----+----+
| 1 | 张三 | 3 | 3 |
+----+------+----+----+
再开启另外一个session,查询其它的数据、
-- session 2
select * from test where name = '张三' for update;
select * from test where name = '李四' for update;
select * from test where name = '王五' for update;
select * from test where name = '赵六' for update;
session2的几个查询结果都阻塞了。
验证加了索引的字段
使用加了索引的字段sn做测试,同样,先开启一个session,锁住一行数据。
-- session 1
begin;
select * from test where sn = 3 for update;
+----+------+----+----+
| id | name | sn | uq |
+----+------+----+----+
| 1 | 张三 | 3 | 3 |
+----+------+----+----+
再开启另外一个session,使用sn作为检索条件查询相同行数据。
-- session 2
begin;
select * from test where sn = 3 for update;
此时SQL运行阻塞,再尝试李四的数据。
-- session 3
begin;
select * from test where sn = 5 for update;
+----+------+----+----+
| id | name | sn | uq |
+----+------+----+----+
| 4 | 李四 | 5 | 5 |
+----+------+----+----+
发现李四可以成功查出,说明InnoDB就是对索引加的锁。
验证锁的传递
此外,MySQL的辅助索引最终都会指向主键索引,那么是否对辅助索引加锁时会同时对主键索引加锁呢?
下面对辅助索引加锁,验证是否可以给同一行的主键索引加锁。
-- session 1
begin;
select * from test where sn = 5 for update;
--session 2
begin;
select * from test where id = 4 for update;
select * from test where uq = 5 for update;
在给说sn=5这一行数据加锁后,下面不管是id=4还是uq=5执行是程序都挂起了,说明对辅助索引加锁时,同时会锁定的主键索引。
1.2.验证加锁算法
- 记录锁(Rrcord Lock):对唯一性索引(主键索引或唯一索引)做等值查询,如果精确的匹配到了一条记录,则锁住这条记录的索引。
- 间隙锁(Gap Lock):对唯一性或普通索引查询的单条记录不存在,则会对此查询条件两端的记录中间的间隙加锁;或精确命中普通索引的一行记录,则会对这行记录两端的间隙及其本身加锁,加锁范围是一个左开右开的区间。
- 临键锁(Next-key Lock):对索引做范围查询,包含记录和区间,对范围内的记录和区间加锁的同时,会对范围内最右一个记录的右侧的区间及右侧的第一个记录加锁。是一个左开右闭的区间。
唯一性索引记录锁
使用主键索引做验证,精确查询某一行已存在的记录,验证是否锁住间隙。
-- session 1
begin;
select * from test where id = 4 for update;
-- session 2
begin;
select * from test where id = 3 for update;
select * from test where id = 4 for update;
select * from test where id = 7 for update;
其中id=4程序阻塞,id=3,7时正常执行,说明锁住的是id=4这个记录的索引。
唯一性索引间隙锁
先删除上面插入的1、2、4、6数据,避免数据影响验证结果。然后精确查询一行不存的数据。
-- session 1
begin;
select * from test where id = 5 for update;
-- session 2
insert into test (id) values (3);
insert into test (id) values (4);
insert into test (id) values (5);
insert into test (id) values (6);
insert into test (id) values (7);
insert into test (id) values (8);
其中id=3,8顺利插入,id=4,7因唯一索引验证插入失败,id=5,6程序阻塞,在sesion1提交后,出现唯一索引验证插入失败。
此时锁住的是(4,7)。
普通索间隙锁
-- session 1
begin;
select * from test where sn = 5 for update;
-- session 2
insert into test (id,sn) values (11,3);
insert into test (id,sn) values (11,4);
insert into test (id,sn) values (11,5);
insert into test (id,sn) values (11,6);
insert into test (id,sn) values (11,7);
此时sn=3,4,5,6时都阻塞,sn=7插入成功,锁住的是[3,7)
唯一性索引临键锁
验证范围查询的加锁情况,主键id分别为1,4,7,10,先查看范围中不包含索引记录的情况,例如 > 4 且 < 7的情况。
-- session 1
begin;
select * from test where id > 4 and id < 7 for update;
-- session 2
insert into test (id) values (3);
insert into test (id) values (4);
insert into test (id) values (5);
insert into test (id) values (6);
insert into test (id) values (7);
insert into test (id) values (8);
此时id=3,8时成功插入,4提示主键重复,5,6,7阻塞,加锁范围是(4,7]。
再验证如果范围包含了7是个什么情况呢?
-- session 1
begin;
select * from test where id > 4 and id <= 7 for update;
-- session 2
insert into test (id) values (3);
insert into test (id) values (4);
insert into test (id) values (7);
insert into test (id) values (8);
insert into test (id) values (10);
insert into test (id) values (11);
除了上面id < 7时的验证结果外,id=8,10的时候也阻塞了,而11可以正常插入。此时加锁范围是(4,10]。
如果范围同样包含了4,会是什么结果呢?是否4之前的间隙和记录也会被锁住呢?
-- session 1
begin;
select * from test where id >= 4 and id <= 7 for update;
-- session 2
insert into test (id) values (3);
insert into test (id) values (4);
insert into test (id) values (11);
验证的3个插入记录中,只有id=4时阻塞,3和11都成功插入,此时加锁范围是[4,10]。
所谓临键锁,就是先找到查询范围内最右侧的索引记录值(升序的末尾记录值),将这个索引记录值的下一个记录值及其它们之间的索引间隙都额外的加上锁。
1.3.验证结论
①只有对唯一性索引做精确查询并命中时,才会加记录锁。
②唯一性索引无法精确查询数据时,或使用普通索引查询时,会加间隙锁。
③如果对索引做范围查询,会锁住间隙和记录,即临键锁。