深入探究MySQL临键锁

本文涉及的产品
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
简介: 临键锁(Next-Key Lock):临键锁是查询时InnoDB根据查询的条件而锁定的一个范围,这个范围中包含有间隙锁和记录数;临键锁=间隙锁+记录锁。其设计的目的是为了解决Phantom Problem(幻读);主要是阻塞insert,但由于临键锁中包含有记录锁,因此临键锁所锁定的范围内如果包含有记录,那么也会给这些记录添加记录锁,从而造成阻塞除insert之外的操作;

2.4 临键锁

2.4.1 临键锁的区间测试

临键锁(Next-Key Lock):临键锁是查询时InnoDB根据查询的条件而锁定的一个范围,这个范围中包含有间隙锁和记录数;临键锁=间隙锁+记录锁

其设计的目的是为了解决Phantom Problem(幻读);主要是阻塞insert,但由于临键锁中包含有记录锁,因此临键锁所锁定的范围内如果包含有记录,那么也会给这些记录添加记录锁,从而造成阻塞除insert之外的操作;

Tips:临键锁的主要目的,也是为了避免幻读(Phantom Read)。如果把事务的隔离级别降级为RC,临键锁则也会失效。

临键锁锁住的区间为:记录+区间(左开右闭)

左开右闭:不锁住左边,锁右边

测试表:

drop table if exists t2;
CREATE TABLE `t2`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `num` int(11) ,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB ;
INSERT INTO `t2`(`id`, `num`) VALUES (5, 5);
INSERT INTO `t2`(`id`, `num`) VALUES (10, 10);
INSERT INTO `t2`(`id`, `num`) VALUES (15, 15);
INSERT INTO `t2`(`id`, `num`) VALUES (20, 20);
-- 创建普通索引
create index idx_num on t2(num);
-- 创建唯一索引
create unique index idx_num on t2(num);
-- 删除索引
drop index idx_num on t2;
  • 区间示意图:

Tips:间隙锁只会阻塞insert,记录锁会阻塞任意的锁(单要注意排他锁和共享锁的关系);

【测试案例-01-间隙锁】

临键锁的触发不仅把条件区间(11-16)的数据行锁住了,还把临键的数据行统统锁住了;锁住的区间为:(10,15]、(15,20]

锁住的id范围:10(不含)~20(含)

session1

session2

begin;

begin;

select * from t2 where id>11 and id<16 for update;

insert into t2 values(10,0); -- 不阻塞

insert into t2 values(11,0); -- 阻塞

insert into t2 values(15,0); -- 阻塞

insert into t2 values(16,0); -- 阻塞

insert into t2 values(18,10); -- 阻塞

insert into t2 values(20,0); -- 阻塞

insert into t2 values(21,0); -- 不阻塞

rollback;

rollback;

【案例测试-02-记录锁】

临键锁是间隙锁+记录数的;上述案例中测试了临键锁中的间隙锁,这次我们来测试一下临键锁中的记录锁;

session1

session2

begin;

begin;

select * from t2 where id>11 and id<16 for update;

select * from t2 where id=12 for update; -- 间隙锁,不阻塞

select * from t2 where id=15 for update; -- 记录数,阻塞

select * from t2 where id=17 for update; -- 间隙锁,不阻塞

select * from t2 where id=20 for update; -- 记录数,不阻塞

rollback;

rollback;

2.4.2 临键锁-普通列

我们刚刚测试的是以主键索引进行测试,如果采用不同的列(普通列、普通索引、唯一索引/主键索引等),则临键锁中的间隙锁和记录锁住的内容大不相同;

如果查询的是普通列,那么触发的临键锁为:表级别的间隙锁+表级别的记录锁

  • 测试表:
drop table if exists t2;
CREATE TABLE `t2`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `num` int(11) ,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB ;
INSERT INTO `t2`(`id`, `num`) VALUES (5, 5);
INSERT INTO `t2`(`id`, `num`) VALUES (10, 10);
INSERT INTO `t2`(`id`, `num`) VALUES (15, 15);
INSERT INTO `t2`(`id`, `num`) VALUES (20, 20);

1)间隙锁

【案例测试-01-表级别间隙锁】

session1

session2

begin;

begin;

select * from t2 where num=11 for update;

insert into t2 values(null,3); -- 阻塞

insert into t2 values(null,5); -- 阻塞

insert into t2 values(null,8); -- 阻塞

insert into t2 values(null,10); -- 阻塞

insert into t2 values(null,18); -- 阻塞

insert into t2 values(null,21); -- 阻塞

rollback;

rollback;

Tips:innoDB查询如果没有使用到索引默认触发表级临键锁,把所有的间隙都锁住了

2)记录锁

以普通列查询除了会触发表级别的临键锁外,同时还会触发表级别的记录锁;

【案例测试-02-表级别记录锁】

session1

session2

begin;

begin;

select * from t2 where num=11 for update;

select * from t2 where id=3 for update; -- 间隙锁,不阻塞

select * from t2 where id=5 for update; -- 记录数,阻塞

select * from t2 where id=8 for update; -- 间隙锁,不阻塞

select * from t2 where id=15 for update; -- 记录数,阻塞

select * from t2 where id=18 for update; -- 间隙锁,不阻塞

select * from t2 where id=20 for update; -- 记录数,阻塞

rollback;

rollback;

2.4.3 临键锁-普通索引

如果查询的列为普通索引列,要看被查询的记录是否在临界值,以及是否是范围查询,才能判断临建锁的范围;

  • 被查询的记录是否在临界值情况:
  • 非临界值:那么间隙锁为当前记录所在的区间,记录锁则不会生效(记录锁不存在);
  • 临界值:那么间隙锁为相邻的两个区间,记录锁退化成行锁(即只会锁住被查询的那条记录);
  • 范围查询情况:间隙锁为范围所涉及到的所有区间,记录锁也会升级为范围锁涉及到的区间
drop table if exists t2;
CREATE TABLE `t2`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `num` int(11) ,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB ;
INSERT INTO `t2`(`id`, `num`) VALUES (5, 5);
INSERT INTO `t2`(`id`, `num`) VALUES (10, 10);
INSERT INTO `t2`(`id`, `num`) VALUES (15, 15);
INSERT INTO `t2`(`id`, `num`) VALUES (20, 20);
-- 创建普通索引
create index idx_num on t2(num);

1)非临界值

当使用普通索引列查询,查询的记录不处于临界值时,那么间隙锁为被查询记录所在的区间,记录锁则不会生效;

【测试案例-01-间隙锁】

session1

session2

begin;

begin;

-- 触发间隙锁,锁住(15,20]区间
select * from t2 where num=17 for update;

insert into t2 values(null,15); -- 阻塞

insert into t2 values(null,18); -- 阻塞

insert into t2 values(null,20); -- 不阻塞

rollback;

rollback;

num=17这条记录不是会锁定(15,20]区间吗?为什么15被阻塞了,20反而没被阻塞呢?

这里需要牵扯到另一个问题了,在InnoDB中,相同的普通索引的叶子节点是以主键的顺序进行排列的,我们来模拟一下刚刚插入的数据在B+Tree上的变化:

只考虑叶子节点的变化,可以看到在上图在演变的过程中产生了分裂情况(假设每个叶子节点都只存储两个元素),如果普通索引的重复值太多势必会造成大量的分裂情况,减低插入效率,因此索引列不宜选择重复率太大的列;

再看下图数据库表中实际存储的列的样子我们就会明白为什么num=20不阻塞,num=15阻塞了

  • num索引列排列情况:

查询示意图:

【测试案例-02-间隙锁】

当我们把id列的影响也计算进来时,数据就符合我们正常分析的情况了:

session1

session2

begin;

begin;

-- 触发间隙锁,锁住(15,20]区间
select * from t2 where num=17 for update;

insert into t2 values(14,15); -- 不阻塞

insert into t2 values(18,18); -- 阻塞

insert into t2 values(19,20); -- 阻塞

rollback;

rollback;

【测试案例-03-记录锁】

当使用普通索引列查询,查询的记录不处于临界值时,那么间隙锁为被查询记录所在的区间,记录锁则不会生效

session1

session2

begin;

begin;

-- 没有num=17的这条记录,记录锁不会存在
select * from t2 where num=17 for update;

select * from t2 where num=15 for update; -- 不阻塞

select * from t2 where num=16 for update; -- 不阻塞

select * from t2 where num=17 for update; -- 不阻塞

select * from t2 where num=20 for update; -- 不阻塞

rollback;

rollback;

2)临界值

【测试案例-01-间隙锁】

当使用普通索引列来查询,并且查询的记录处于临界值时,那么间隙锁为相邻的两个区间,记录锁退化成行锁;

下面案例将会锁住(10,15](15,20]两个区间

session1

session2

begin;

begin;

-- 触发的间隙锁的区间为(10,15]、(15,20]
select * from t2 where num=15 for update;

insert into t2 values(null,8); -- 不阻塞

insert into t2 values(null,10); -- 阻塞

insert into t2 values(null,11); -- 阻塞

insert into t2 values(null,15); -- 阻塞

insert into t2 values(null,18); -- 阻塞

insert into t2 values(null,20); -- 不阻塞

rollback;

rollback;

发现实际插入的数据跟我们分析的情况不一致,这个时候我们依然也要观察B+Tree的实现:

  • 索引底层构建过程:

  • 临键锁区间:

15处于(10,15](15,20]两个临键区间,因此在两个区间内的数据行都被锁住了

【测试案例-02-记录锁】

当使用普通索引列来查询,并且查询的记录处于临界值时,那么间隙锁为相邻的两个区间,记录锁退化成行锁

session1

session2

begin;

begin;

-- 记录锁只锁住num=15这行记录
select * from t2 where num=15 for update;

select * from t2 where num=10 for update; -- 不阻塞

select * from t2 where num=12 for update; -- 不阻塞

select * from t2 where num=15 for update; -- 阻塞

select * from t2 where num=18 for update; -- 不阻塞

select * from t2 where num=20 for update; -- 不阻塞

select * from t2 where num=22 for update; -- 不阻塞

rollback;

rollback;

3)范围值

【测试案例-01-间隙锁】

当使用普通索引进行条件范围查询时,那么间隙锁查询范围所涉及到的区间,记录锁也会升级为查询范围涉及到的区间;

session1

session2

begin;

begin;

-- 间隙锁为(10,20]区间
select * from t2 where num>11 and num <16 for update;

insert into t2 values(9,10); -- 不阻塞

insert into t2 values(11,10); -- 阻塞(参考B+Tree的构建)

insert into t2 values(11,11); -- 阻塞

insert into t2 values(12,12); -- 阻塞

insert into t2 values(15,15); -- 阻塞(被记录锁阻塞)

insert into t2 values(18,18); -- 阻塞

insert into t2 values(19,20); -- 阻塞

insert into t2 values(21,20); -- 不阻塞(参考B+Tree的构建)

rollback;

rollback;

  • 分析底层B+Tree构建情况:

【测试案例-02-记录锁】

当使用普通索引进行条件范围查询时,那么间隙锁查询范围所涉及到的区间,记录锁也会升级为查询范围涉及到的区间;

session1

session2

begin;

begin;

-- 记录锁的区间为(10,20]区间
select * from t2 where num>11 and num <16 for update;

select * from t2 where num=10 for update; -- 不阻塞(左开右闭)

select * from t2 where num=12 for update; -- 不阻塞(属于间隙)

select * from t2 where num=15 for update; -- 阻塞(触发记录锁)

select * from t2 where num=16 for update; -- 不阻塞(属于间隙)

select * from t2 where num=18 for update; -- 不阻塞(属于间隙)

select * from t2 where num=20 for update; -- 阻塞(左开右闭,触发记录锁)

select * from t2 where num=21 for update; -- 不阻塞(即是间隙,也不在区间)

rollback;

rollback;

2.4.4 临键锁-主键和唯一索引

如果查询的是唯一索引或主键索引,也要看被查询的记录是否在临界值;是否是范围查询等

  • 被查询的记录是否在临界值情况:
  • 不在临界值:间隙锁为当前被查询的记录所在的区间,记录锁会消失;
  • 在临界值:间隙锁会消失,记录锁退化成行锁
  • 范围查询情况:间隙锁为范围查询所涉及到的所有区间,记录数也会升级为范围所涉及到的区间(和普通索引的效果一致);

创建唯一索引:

-- 删除索引
drop index idx_num on t2;
-- 创建唯一索引
create unique index idx_num on t2(num);

1)非临界值

唯一索引在查询非临界值的记录时和普通索引的特点一样,即==间隙锁为当前记录所在的区间,记录锁不生效;==

【测试案例-01-间隙锁】

session-01

session-02

begin;

begin;

-- 间隙锁锁住的区间为(15,20]
select * from t2 where num=17 for update;

insert into t2 values(null,11); -- 不阻塞

insert into t2 values(null,15); -- 不阻塞

insert into t2 values(null,16); -- 阻塞

insert into t2 values(null,18); -- 阻塞

insert into t2 values(null,20); -- 不阻塞

insert into t2 values(null,21); -- 不阻塞

  • 分析num列索引的B+Tree底层构建情况:

Tips:唯一索引冲突时MySQL会立即响应,不会触发临键锁

【测试案例-02-记录锁】

唯一索引在查询非临界值的记录时,记录锁不生效;

session1

session2

begin;

begin;

-- 没有num=17的这条记录,记录锁不会存在
select * from t2 where num=17 for update;

select * from t2 where num=15 for update; -- 不阻塞

select * from t2 where num=16 for update; -- 不阻塞

select * from t2 where num=17 for update; -- 不阻塞

select * from t2 where num=20 for update; -- 不阻塞

rollback;

rollback;

2)临界值

在使用唯一索引查询临界值时,间隙锁会消失,记录锁会退化成行锁;

【测试案例-01-间隙锁】

session1

session2

begin;

begin;

select * from t2 where num=15 for update;

insert into t2 values(null,4); -- 不阻塞

insert into t2 values(null,8); -- 不阻塞

insert into t2 values(null,11); -- 不阻塞

insert into t2 values(null,15); -- 阻塞(阻塞的原因是记录锁,而不是间隙锁)

insert into t2 values(null,28); -- 不阻塞

rollback;

insert into t2 values(null,20); -- 不阻塞

rollback;

【测试案例-02-记录锁】

session1

session2

begin;

begin;

-- 记录锁只锁住num=15这行记录
select * from t2 where num=15 for update;

select * from t2 where num=10 for update; -- 不阻塞

select * from t2 where num=12 for update; -- 不阻塞

select * from t2 where num=15 for update; -- 阻塞

select * from t2 where num=18 for update; -- 不阻塞

select * from t2 where num=20 for update; -- 不阻塞

select * from t2 where num=22 for update; -- 不阻塞

rollback;

rollback;

3)范围值

【测试案例-01-间隙锁】

当使用普通索引进行条件范围查询时,那么间隙锁查询范围所涉及到的区间,记录锁也会升级为查询范围涉及到的区间;

session1

session2

begin;

begin;

-- 间隙锁为(10,20]区间
select * from t2 where num>11 and num <16 for update;

insert into t2 values(9,10); -- 不阻塞

insert into t2 values(11,10); -- 阻塞(参考B+Tree的构建)

insert into t2 values(11,11); -- 阻塞

insert into t2 values(12,12); -- 阻塞

insert into t2 values(15,15); -- 阻塞(被记录锁阻塞)

insert into t2 values(18,18); -- 阻塞

insert into t2 values(19,20); -- 阻塞

insert into t2 values(21,20); -- 不阻塞(参考B+Tree的构建)

rollback;

rollback;

  • 分析底层B+Tree构建情况:

【测试案例-02-记录锁】

当使用普通索引进行条件范围查询时,那么间隙锁查询范围所涉及到的区间,记录锁也会升级为查询范围涉及到的区间;

session1

session2

begin;

begin;

-- 记录锁的区间为(10,20]区间
select * from t2 where num>11 and num <16 for update;

select * from t2 where num=10 for update; -- 不阻塞(左开右闭)

select * from t2 where num=12 for update; -- 不阻塞(属于间隙)

select * from t2 where num=15 for update; -- 阻塞(触发记录锁)

select * from t2 where num=16 for update; -- 不阻塞(属于间隙)

select * from t2 where num=18 for update; -- 不阻塞(属于间隙)

select * from t2 where num=20 for update; -- 阻塞(左开右闭,触发记录锁)

select * from t2 where num=21 for update; -- 不阻塞(即是间隙,也不在区间)

rollback;

rollback;

2.4.5 临键锁总结

临键锁是InnoDB在查询数据时锁定的一个范围,这个范围包含有间隙锁和记录锁;根据查询的条件不同、列的类型不同(是否是索引等)触发的临键锁范围也不同;

  • 普通列:临键锁中的间隙锁和记录数均为表级别;
  • 普通索引列:
  • 非临界值:间隙锁为被查询的记录所在的区间,记录锁不生效
  • 临界值:间隙锁为被查询记录所在的相邻两个区间,记录数退化为行锁
  • 范围值:间隙锁和记录数均为查询条件所涉及到的区间
  • 唯一索引或主键索引列:
  • 非临界值:间隙锁为被查询的记录所在的区间,记录锁不生效
  • 临界值:间隙锁失效,记录锁退化为行锁
  • 范围值:间隙锁和记录数均为查询条件所涉及到的区间

Tips:临键锁的主要目的,也是为了避免幻读(Phantom Read)。如果把事务的隔离级别降级为RC,临键锁则也会失效。

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
7月前
|
SQL AliSQL 关系型数据库
MYSQL的全局锁和表锁
本文介绍了MySQL中的锁机制,包括全局锁、表级锁及其应用场景。全局锁通过`Flush tables with read lock (FTWRL)`实现,主要用于全库逻辑备份,但会阻塞更新和结构变更操作。表级锁分为显式表锁(`lock tables`)和元数据锁(MDL),前者用于控制并发访问,后者自动加锁以确保读写正确性。文章还探讨了如何安全地为小表添加字段,建议通过设置DDL等待时间或使用MariaDB/AliSQL的NOWAIT/WAIT功能避免业务阻塞。这些方法有助于在高并发场景下优化数据库性能与安全性。
204 0
|
SQL 关系型数据库 MySQL
MySQL 锁
MySQL里常见的几种锁
235 3
|
10月前
|
关系型数据库 MySQL 网络安全
如何排查和解决PHP连接数据库MYSQL失败写锁的问题
通过本文的介绍,您可以系统地了解如何排查和解决PHP连接MySQL数据库失败及写锁问题。通过检查配置、确保服务启动、调整防火墙设置和用户权限,以及识别和解决长时间运行的事务和死锁问题,可以有效地保障应用的稳定运行。
420 25
|
11月前
|
存储 关系型数据库 MySQL
MySQL进阶突击系列(06)MySQL有几种锁?| 别背答案,现场演示一下
本文详细解析了MySQL InnoDB存储引擎的锁机制,涵盖读锁、写锁、意向锁、记录锁、间隙锁和临键锁等8种锁类型。重点探讨了不同锁类型的加锁与释放方式,以及事务并发场景下的实战验证。通过具体示例,展示了在不同情况下锁的行为及其对事务的影响。文章还特别强调了锁的作用范围主要是索引,并解释了锁如何影响数据的读写操作。最后总结了并发事务中加锁规则,帮助读者深入理解MySQL的锁机制。
|
存储 关系型数据库 MySQL
优化 MySQL 的锁机制以提高并发性能
【10月更文挑战第16天】优化 MySQL 锁机制需要综合考虑多个因素,根据具体的应用场景和需求进行针对性的调整。通过不断地优化和改进,可以提高数据库的并发性能,提升系统的整体效率。
745 1
|
关系型数据库 MySQL Java
MySQL数据锁:Record Lock,Gap Lock 和 Next-Key Lock
本文基于 MySQL 8.0.30 版本及 InnoDB 引擎,深入解析三种行锁机制:记录锁(Record Lock)、间隙锁(Gap Lock)和临键锁(Next-key Lock)。记录锁锁定索引记录,确保事务唯一修改;间隙锁锁定索引间的间隙,防止新记录插入;临键锁结合两者,锁定范围并记录自身,有效避免幻读现象。通过具体示例展示了不同锁的作用机制及其在并发控制中的应用。
1276 2
|
存储 关系型数据库 MySQL
MySQL数据库锁:共享锁和独占锁
本文详细介绍了`InnoDB`存储引擎中的两种行级别锁:共享锁(S锁)与排他锁(X锁)。通过具体示例展示了这两种锁的工作机制及其在`InnoDB`与`MyISAM`引擎中的表现差异。文章还提供了锁的兼容性矩阵,帮助读者更好地理解锁之间的互斥关系。最后总结了两种锁的特点及适用场景。适合希望深入了解`MySQL`并发控制机制的读者阅读。
501 1
|
监控 关系型数据库 MySQL
MySQL锁机制与解决死锁问题
MySQL锁机制与解决死锁问题
646 5
|
关系型数据库 MySQL 数据库
Mysql的锁
本文介绍了MySQL中表级锁和行级锁的区别,其中MyISAM仅支持表级锁,而InnoDB支持表级锁和行级锁,默认为行级锁。表级锁锁定整个表,实现简单,资源消耗少,但并发度低;行级锁仅锁定相关记录,减少冲突,提高并发度,但加锁开销大。此外,还介绍了共享锁和排他锁的概念及意向锁的作用。
135 1
|
存储 关系型数据库 MySQL
MySQL锁,锁的到底是什么?
【10月更文挑战第16天】MySQL 锁锁定的是与数据和资源相关的对象,其目的是为了保证数据的一致性、避免冲突,并在并发环境下合理协调事务或操作的执行。理解锁的对象和意义对于优化数据库性能、处理并发问题至关重要。
362 0

相关产品

  • 云数据库 RDS MySQL 版
  • 推荐镜像

    更多