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. 插入意向锁与间隙锁冲突,是死锁的常见原因

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

相关推荐
松涛和鸣2 小时前
72、IMX6ULL驱动实战:设备树(DTS/DTB)+ GPIO子系统+Platform总线
linux·服务器·arm开发·数据库·单片机
likangbinlxa2 小时前
【Oracle11g SQL详解】UPDATE 和 DELETE 操作的正确使用
数据库·sql
r i c k3 小时前
数据库系统学习笔记
数据库·笔记·学习
野犬寒鸦3 小时前
从零起步学习JVM || 第一章:类加载器与双亲委派机制模型详解
java·jvm·数据库·后端·学习
IvorySQL4 小时前
PostgreSQL 分区表的 ALTER TABLE 语句执行机制解析
数据库·postgresql·开源
·云扬·4 小时前
MySQL 8.0 Redo Log 归档与禁用实战指南
android·数据库·mysql
野生技术架构师4 小时前
SQL语句性能优化分析及解决方案
android·sql·性能优化
IT邦德4 小时前
Oracle 26ai DataGuard 搭建(RAC到单机)
数据库·oracle
惊讶的猫5 小时前
redis分片集群
数据库·redis·缓存·分片集群·海量数据存储·高并发写