Next-Key Lock、记录锁、间隙锁浅谈

一、核心关系与概述

Next-Key Lock = 记录锁 + 间隙锁

这三者共同构成了 InnoDB 在 可重复读(Repeatable Read) 隔离级别下的锁定机制,用于解决并发事务中的各种数据一致性问题。

二、记录锁(Record Lock)

定义

记录锁是锁住索引记录本身的锁。当一条记录被锁定后,其他事务无法修改或删除这条记录。

特性

  1. 锁定对象:索引记录(不是数据行本身,而是索引条目)

  2. 作用:防止其他事务修改或删除已锁定的记录

  3. 兼容性

    • 共享记录锁(S)与共享记录锁兼容

    • 排他记录锁(X)与任何记录锁都不兼容

  4. 加锁场景

    • SELECT ... FOR UPDATE

    • SELECT ... LOCK IN SHARE MODE

    • UPDATEDELETE 语句

示例

sql 复制代码
-- 事务 A
START TRANSACTION;
SELECT * FROM users WHERE id = 10 FOR UPDATE;
-- 对 id=10 的记录加排他记录锁

-- 事务 B(被阻塞)
UPDATE users SET name = 'Bob' WHERE id = 10;
-- 需要等待事务 A 释放锁

三、间隙锁(Gap Lock)

定义

间隙锁锁定的是索引记录之间的间隙,防止在这个范围内插入新记录。

特性

  1. 锁定对象:索引记录之间的间隙

  2. 作用:防止幻读(Phantom Read)

  3. 锁定区间 :左开右开区间 (a, b)

  4. 兼容性

    • 间隙锁之间不冲突(多个事务可以同时持有相同间隙的间隙锁)

    • 间隙锁与插入意向锁冲突

  5. 加锁场景

    • 范围查询

    • 等值查询未命中时

示例

sql 复制代码
-- 表 users: id 索引,记录有 5, 10, 15

-- 事务 A
START TRANSACTION;
SELECT * FROM users WHERE id > 10 AND id < 15 FOR UPDATE;
-- 锁定间隙 (10, 15)

-- 事务 B(被阻塞)
INSERT INTO users (id, name) VALUES (12, 'Alice');
-- 尝试插入 id=12,位于间隙 (10, 15) 内,被阻塞

-- 事务 C(不阻塞)
INSERT INTO users (id, name) VALUES (20, 'Bob');
-- id=20 不在锁定范围内,可以正常插入

四、Next-Key Lock

定义

Next-Key Lock 是 InnoDB 的默认行锁算法,它结合了记录锁和间隙锁,锁定记录本身以及记录之前的间隙

特性

  1. 锁定对象:记录 + 前一个间隙

  2. 锁定区间 :左开右闭区间 (a, b]

  3. 作用 :同时防止不可重复读幻读

  4. 默认行为:InnoDB 在可重复读隔离级别下的默认锁定方式

  5. 扫描过程

    • 从第一个满足条件的记录开始

    • 对扫描到的记录加 Next-Key Lock

    • 继续扫描直到第一个不满足条件的记录,并对其加间隙锁

示例

sql 复制代码
-- 表 users: id 主键,记录有 5, 10, 15, 20

-- 事务 A
START TRANSACTION;
SELECT * FROM users WHERE id >= 10 AND id < 15 FOR UPDATE;

-- 锁定的范围:
-- 1. 找到 id=10:锁定 Next-Key Lock (5, 10]
-- 2. 找到 id=15(不满足条件):锁定间隙锁 (10, 15)
-- 实际效果:锁定范围 (5, 15)

-- 事务 B(不同操作的阻塞情况)
-- 1. 被阻塞:插入 id=8(在 (5, 10] 范围内)
-- 2. 被阻塞:插入 id=12(在 (10, 15) 范围内)
-- 3. 通过:插入 id=3(不在锁定范围内)
-- 4. 被阻塞:更新 id=10(记录锁冲突)

五、不同查询场景下的锁定行为

1. 唯一索引等值查询

sql 复制代码
-- id 是唯一索引,记录 id=10 存在
SELECT * FROM users WHERE id = 10 FOR UPDATE;
-- 锁定:仅对 id=10 加记录锁(Next-Key Lock 退化为记录锁)

2. 唯一索引等值查询未命中

sql 复制代码
-- id 是唯一索引,记录 id=12 不存在
SELECT * FROM users WHERE id = 12 FOR UPDATE;
-- 假设存在记录 10 和 15
-- 锁定:间隙锁 (10, 15)

3. 非唯一索引等值查询

sql 复制代码
-- age 是非唯一索引,age=20 的记录存在
SELECT * FROM users WHERE age = 20 FOR UPDATE;
-- 锁定:
-- 1. 对 age=20 的所有记录加 Next-Key Lock
-- 2. 对 age=20 后的第一个不满足条件的记录加间隙锁

4. 范围查询

sql 复制代码
-- id 是主键
SELECT * FROM users WHERE id BETWEEN 10 AND 20 FOR UPDATE;
-- 锁定:
-- 1. id=10:Next-Key Lock (前一个id, 10]
-- 2. 扫描到 id=20:Next-Key Lock (15, 20]
-- 3. 继续扫描到 id=25(不满足条件):间隙锁 (20, 25)

六、特殊情况与优化

1. 锁降级(Lock Degradation)

唯一索引等值查询命中

sql 复制代码
-- id 是唯一索引,且 id=10 存在
SELECT * FROM users WHERE id = 10 FOR UPDATE;
-- Next-Key Lock 退化为记录锁(只锁 id=10)
-- 因为唯一性保证不会有幻读

2. 锁升级

无索引查询

sql 复制代码
-- name 列无索引
SELECT * FROM users WHERE name = 'Alice' FOR UPDATE;
-- 无法使用行锁,会锁定全表的所有记录和间隙
-- 性能极差,应避免

3. 插入意向锁(Insert Intention Lock)

sql 复制代码
-- 事务 A
SELECT * FROM users WHERE id > 10 FOR UPDATE;
-- 锁定间隙 (10, +∞)

-- 事务 B
INSERT INTO users (id, name) VALUES (15, 'Bob');
-- 需要获取插入意向锁,与事务 A 的间隙锁冲突
-- 事务 B 被阻塞

七、死锁示例分析

sql 复制代码
-- 表结构:id 主键,记录有 5, 10

-- 事务 A
START TRANSACTION;
SELECT * FROM users WHERE id = 10 FOR UPDATE;  -- 锁定 id=10

-- 事务 B
START TRANSACTION;
SELECT * FROM users WHERE id = 5 FOR UPDATE;   -- 锁定 id=5

-- 事务 A
INSERT INTO users (id, name) VALUES (7, 'Alice');
-- 需要获取间隙锁 (5, 10),但事务 B 持有 id=5,可能涉及间隙锁
-- 等待事务 B

-- 事务 B
INSERT INTO users (id, name) VALUES (7, 'Bob');
-- 需要获取间隙锁 (5, 10),但事务 A 持有 id=10
-- 等待事务 A
-- 死锁发生!

八、实践建议

1. 索引设计

  • 查询条件尽量使用索引

  • 避免全表扫描导致锁表

  • 合理设计唯一索引和非唯一索引

2. 事务设计

  • 尽量缩短事务时间

  • 避免在事务中执行长时间的操作

  • 按固定顺序访问资源,避免死锁

3. SQL 优化

  • 精确的查询条件减少锁定范围

  • 避免范围查询锁定过多记录

  • 使用 FOR UPDATELOCK IN SHARE MODE 要谨慎

4. 监控与排查

sql 复制代码
-- 查看当前锁信息
SHOW ENGINE INNODB STATUS;

-- 查看锁等待
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;

九、总结对比表

特性 记录锁 间隙锁 Next-Key Lock
锁定对象 索引记录 索引间隙 记录 + 前一个间隙
锁定区间 单点 (a, b) 开区间 (a, b] 左开右闭
主要目的 防止修改/删除 防止插入(幻读) 防止修改/删除和插入
兼容性 记录锁间按规则冲突 间隙锁间不冲突 按包含的记录锁和间隙锁判断
使用场景 等值查询命中 范围查询、等值查询未命中 InnoDB 默认行锁算法
隔离级别 所有支持行锁的级别 RR 及以上(RC 通常禁用) RR 级别默认使用

十、关键要点

  1. Next-Key Lock 是 InnoDB 防止幻读的核心机制

  2. 间隙锁只在 RR 隔离级别有效(RC 级别通常不使用间隙锁)

  3. 唯一索引可以优化锁定范围(等值查询时退化为记录锁)

  4. 无索引查询会导致锁表,性能极差

  5. 插入意向锁与间隙锁冲突,是死锁的常见原因

理解这三种锁机制对于设计高性能、高并发的数据库应用至关重要,特别是在需要处理复杂事务的场景下。

相关推荐
做cv的小昊20 小时前
【TJU】信息检索与分析课程笔记和练习(7)数据库检索—Ei
数据库·笔记·学习·全文检索
zgl_2005377920 小时前
ZGLanguage 解析SQL数据血缘 之 标识提取SQL语句中的目标表
java·大数据·数据库·数据仓库·hadoop·sql·源代码管理
莳花微语20 小时前
记录一次OGG进程abended,报错OGG-01431、OGG-01003、OGG-01151、OGG-01296问题的处理
数据库·sql·mysql
尋有緣21 小时前
力扣1355-活动参与者
大数据·数据库·leetcode·oracle·数据库开发
萧曵 丶21 小时前
MySQL三大日志系统浅谈
数据库·sql·mysql
煎蛋学姐21 小时前
SSM校园兼职招聘系统x6u36(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·企业管理·ssm 框架·校园兼职招聘系统
ChineHe1 天前
Redis基础篇004_Redis Pipeline流水线详解
数据库·redis·缓存
西柚补习生1 天前
通用 PWM 原理基础教学
数据库·mongodb
小张程序人生1 天前
ShardingJDBC读写分离详解与实战
数据库