MySQL 锁机制系列(一) —— Next-Key Lock

本文详细介绍了MySQL中Next-Key Lock的概念、作用以及在不同场景下的锁行为,包括主键查询、范围查询、普通索引等。通过实例分析了锁的范围,如(1,10)、(10,30)等,并提到了如何查看数据库中的锁情况和事务信息。" 123612751,12861469,Android ADB详解与实战,"['Android开发', '开发语言', '工具使用']

背景故事

事务A事务B
begin;
select count(*) from t_user where uid > 20;
// 返回1
insert into t_user values(30,‘AA’,20);
update t_user set age = 30 where uid > 20;
// 显示有两条记录修改成功

上面两个事务,首先先开启事务A,查询id大于20的记录数,结果返回1,说明只有1条记录uid大于20,随后事务B插入了一条uid为30的记录并提交,此时事务A开始执行修改操作,将所有uid大于20的记录的age字段修改为30,执行后显示有两条记录修改成功。这就是常说的幻读形象。

而常见的解决幻读的方法有两种:

  1. 将隔离级别升级到SERIALIZABLE;
  2. 加锁。

一般不会将数据库隔离级别设置为SERIALIZABLE,那么加锁的话,加什么锁呢?Next-Key Lock。

Next-Key Lock是什么?Next-Key Lock是如何解决幻读的?

下面就来仔细看看。

InnoDB下有三种锁算法:

  • Record Lock :单个行记录加锁
  • Gap Lock :间隙锁,锁定一个范围,但不含记录本身
  • Next-Key Lock :Record Lock + Gap Lock

MySQL InnoDB存储引擎中默认使用的是Next-Key Lock,先使用Gap Lock,当通过对应唯一索引查询时,会将Gap Lock降级为Record Lock。

假设有如下数据:
在这里插入图片描述

节点结构可以简单看作如下形式:

在这里插入图片描述

在上面数据中,Recored Lock对应的就是这三个节点,也就是对应uid为1,10,30的记录,而Gap Lock就是这几个节点之间的范围。整个锁范围可以分为(-∞,1) U 1 U (1,10) U 10 U (10,30) U 30 U (30,+∞)。

下面通过特定的几个场景来看下实际的效果:

表结构

CREATE TABLE `t_user` (
  `uid` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `uname` varchar(30) DEFAULT NULL COMMENT '用户名',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  PRIMARY KEY (`uid`),
  KEY `index_age` (`age`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8;

以下示例都是在默认隔离级别REPEATABLE-READ下,不同隔离级别效果不同。

PS :每次修改数据后,都会将数据恢复到最初的状态。

1. 主键对应记录存在

事务A事务B
begin
select * from t_user where uid = 10 for update;
对UID为10的记录加锁
insert into t_user values(7,‘DD’,‘111111’);
执行成功
update t_user set uname = ‘bbb’ where uid = 10;
对UID为10的记录进行修改
阻塞直到锁超时
update t_user set uname = ‘bbb’ where uid = 10;
对UID为10的记录再次进行修改
commit;
此时是释放锁
阻塞
Query OK, 1 row affected (7.14 sec)
Rows matched: 1 Changed: 1 Warnings: 0
成功执行修改操作

当对应主键记录存在时,只会对对应记录行加Recored Lock。

2. 主键对应记录不存在

事务A事务B事务C
begin;
select * from t_user where uid = 8 for update;
UID为8的记录不存在
update t_user set uname = ‘DDD’ where uid = 10;
直接执行成功
insert into t_user values(7,‘DD’,‘111111’);insert into t_user values(8,‘DD’,‘111111’);
阻塞阻塞
commit;
Query OK, 1 row affected (3.69 sec)Query OK, 1 row affected (3.69 sec)

对应主键不存在的记录,会加对应的间隙锁,因为uid8的值大于1小于10,所以为在两者之间加间隙锁,范围也就是(1,10)。

3. 通过主键进行范围查询

事务A事务B事务C
begin;
select * from t_user where uid > 12 for update;
insert into t_user values(8,‘DD’,‘111111’);
执行成功
update t_user set uname = ‘DDD’ where uid = 10;
执行成功
insert into t_user values(11,‘DD’,‘111111’);update t_user set uname = ‘DDD’ where uid = 30;
阻塞阻塞
commit;
Query OK, 1 row affected (15.71 sec)Query OK, 1 row affected (12.78 sec)

当对范围查询加锁时,会将对应的记录行加行锁以及间隙锁,上面加锁范围为 (10,30) U 30 U (30,+∞),合起来就是(10,+∞)。

4. 对应记录存在 —— 普通索引

事务A事务B事务C
begin;
select * from t_user where age = 30 for update;
insert into t_user values(7,‘DD’,66);
执行成功
update t_user set uname = ‘EE’ where uid = 10;insert into t_user values(8,‘DD’,20);
阻塞阻塞
commit;
Query OK, 1 row affected (14.83 sec)Query OK, 1 row affected (14.83 sec)

通过普通索引查询加锁时,除了会将对应的记录行加Record Lock,还会对对应的普通索引字段内容加锁,上面加锁范围为age 在10U(10,30) U 30 U (30,50) U 50 之间,也就是[10,50],再加上UID为10的记录。

5. 对应记录不存在 —— 普通索引

事务A事务B
begin;
select * from t_user where age = 40 for update;
insert into t_user values(7,‘DD’,66);
执行成功
insert into t_user values(8,‘DD’,44);
阻塞
commit;
Query OK, 1 row affected (2.86 sec)

当不存在对应的行记录时,只会对普通索引字段加锁,age范围为(30,50) U 50,也就是(30,50]。

6. 普通索引范围查询

事务A事务B
begin;
select * from t_user where age > 40 for update;
insert into t_user values(7,‘DD’,30);
执行成功
insert into t_user values(8,‘DD’,33);
阻塞
commit;
Query OK, 1 row affected (47.57 sec)

锁范围为(30, 50) U 50 U (50,+∞),合并后就是(30,+∞)。

7. 非索引字段

事务A事务B
begin;
select * from t_user where uname = ‘BB’ for update;
insert into t_user values(6,‘DD’,51);
阻塞
commit;
Query OK, 1 row affected (4.01 sec)

SERIALIZABLE事务都是串行的,一般也不会使用该隔离级别。REPEATABLE_READ和READ_COMMITTED一样都会使用Next-Key Lock。但是READ_UNCOMMITTED不存在Gap Lock,只会对对应的记录行加锁。

附录:

1. 查询当前数据库中锁情况:

select * from information_schema.INNODB_LOCKS;

在这里插入图片描述

  • lock_id :锁Id,事务ID+space id+lock_page+lock_rec
  • lock_trx_id :事务ID
  • lock_mode :锁模式
  • lock_type : 锁类型,行锁还是表锁
  • lock_table :加锁表
  • lock_index :加锁索引
  • lock_space : 锁对象的space id
  • lock_page :事务锁定页的数量,若为表锁则为null
  • lock_rec : 事务锁定行的数量,若为表锁则为null
  • lock_data :事务锁定的主键值,若为表锁则为null

为什么单个事务中加锁查询无结果,当同一个锁存在其他事务等待时,才会出现。

2. 查询当前数据库锁等待情况

select * from information_schema.INNODB_LOCK_WAITS;

在这里插入图片描述

  • requesting_trx_id :申请锁资源事务ID
  • requested_lock_id :申请锁ID
  • blocking_trx_id :当前锁资源事务ID
  • blocking_lock_id :当前锁ID

3. 查询当前数据库事务信息

select * from information_schema.INNODB_TRX;

<think>好的,我现在需要回答用户关于MySQL中页级锁和Next-Key Lock的区别及应用场景的问题。首先,我需要明确这两个概念的定义和它们在InnoDB中的角色。 首先,页级锁,根据我之前学过的知识,页级锁是MySQL种锁定机制,锁定整个数据页。数据页是InnoDB存储数据的基本单位,通常大小为16KB。页级锁的粒度介于表锁和行锁之间,它会影响整个页内的所有行,即使只访问其中行。这种锁的优点是开销比行锁小,冲突概率比表锁低,但缺点是不够精细,可能导致不必要的锁定,影响并发性能。应用场景可能是在需要平衡并发和锁开销的情况下,比如批量更新同页内的多行数据时。 然后是Next-Key Lock,这是InnoDB为了实现可重复读隔离级别而设计的锁机制,结合了记录锁(行锁)和间隙锁。Next-Key Lock锁定的是索引记录之间的间隙以及记录本身,防止幻读的发生。例如,当查询某个范围的数据时,Next-Key Lock会锁定这个范围以及相邻的间隙,确保其他事务不能插入新的数据到这个范围内。根据引用[4]提到的加锁规则,Next-Key Lock是前开后闭区间,并且在某些情况下会退化为行锁或间隙锁。 接下来,我需要区分两者的区别。首先是锁的粒度:页级锁锁定整个页,而Next-Key Lock锁定的是索引记录和间隙,属于行级锁的范畴。锁的用途方面,页级锁用于控制对物理页的访问,而Next-Key Lock用于解决幻读问题,保证事务的隔离性。在并发控制上,页级锁可能因为锁住更多数据而导致并发度较低,而Next-Key Lock虽然更精细,但由于需要维护更多锁,可能有更高的开销。应用场景方面,页级锁适合批量操作同页的数据,而Next-Key Lock用于需要防止幻读的高并发事务环境。 还需要考虑用户提供的引用内容。引用[1]提到InnoDB的三种行锁:间隙锁、行锁和Next-Key Lock,说明Next-Key Lock属于行级锁的部分。引用[4]详细说明了Next-Key Lock的加锁规则,包括退化为行锁或间隙锁的情况,这有助于解释其灵活性。而页级锁并不属于行锁,是另种粒度的锁,可能在较早的MySQL版本或特定场景中使用较多,但现在InnoDB默认使用行级锁和Next-Key Lock来实现更高并发。 用户可能混淆了不同粒度的锁,需要明确页级锁和行级锁(包括Next-Key Lock)的不同层级。此外,应用场景的对比需要具体举例,比如页级锁在批量处理时的效率,而Next-Key Lock在事务安全中的重要性。 最后,确保回答结构清晰,分点说明概念、区别、应用场景,并生成相关问题。同时检查是否需要引用,比如引用[4]提到的加锁规则,可以在解释Next-Key Lock时引用。</think>### 页级锁与Next-Key Lock的概念及区别 #### 、基本概念 1. **页级锁** - **定义**:锁定InnoDB存储引擎中的物理数据页(默认16KB),属于中粒度锁。 - **特点**:无论访问页内多少行数据,都会锁定整个页。 - **应用场景**:适用于批量操作同页内多行数据的场景(如旧版本MySQL的特定优化)。 2. **Next-Key Lock** - **定义**:InnoDB在可重复读(Repeatable Read)隔离级别下使用的锁机制,**结合记录锁(行锁)和间隙锁**,防止幻读[^2][^4]。 - **特点**: - 锁定范围:索引记录本身(行锁) + 索引前的间隙(间隙锁),形成**前开后闭区间**,例如`(5, 10]`[^4]。 - 根据查询条件可能退化为**行锁**(唯索引等值查询)或**间隙锁**(非唯索引等值查询)[^4]。 --- #### 二、核心区别 | **维度** | **页级锁** | **Next-Key Lock** | |-------------------|--------------------------------|-----------------------------------| | **锁粒度** | 物理页(16KB) | 索引记录及间隙(行级锁范畴) | | **锁目标** | 控制物理页的并发访问 | 解决幻读,保证事务隔离性 | | **并发性能** | 较低(锁定更多数据) | 较高(更精细,但锁数量可能更多) | | **应用场景** | 批量操作同页数据 | 高并发事务中防止幻读 | | **实现层级** | 存储引擎物理层 | 索引逻辑层 | --- #### 三、应用场景 1. **页级锁** - **适用场景**: - 历史版本MySQL中需要平衡锁开销与并发性能的场景。 - 批量更新或删除同页内的连续数据(如日志表清理)。 - **局限性**:现代InnoDB默认使用行级锁,页级锁逐渐被替代。 2. **Next-Key Lock** - **适用场景**: - 事务需要避免幻读(如银行转账、库存扣减)。 - 范围查询(`WHERE id BETWEEN 10 AND 20`)或非唯索引的等值查询。 - **示例**: ```sql -- 对索引列age=20的查询会锁定(15,20]的Next-Key Lock SELECT * FROM users WHERE age = 20 FOR UPDATE; ``` --- #### 四、总结对比 - **页级锁**关注**物理存储单元**的并发控制,适合**粗粒度操作**。 - **Next-Key Lock**关注**逻辑数据致性**,通过精细的行级锁实现事务安全。 - **现代InnoDB更依赖Next-Key Lock**,因其能有效支持高并发事务场景。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值