Skip to content

临键锁 (Next-Key Lock) ​

📋 概述 ​

临键锁(Next-Key Lock)是 MySQL InnoDB 引擎在 REPEATABLE READ 隔离级别下使用的默认锁算法。它是记录锁(Record Lock)和间隙锁(Gap Lock)的组合,既能锁定索引记录本身,又能锁定记录之前的间隙。

核心特点 ​

✅ 优点:

  • 彻底防止幻读: 同时锁定记录和间隙
  • 保证可重复读: 确保事务内多次查询结果一致
  • 默认启用: InnoDB 的默认行为,无需配置
  • 左开右闭区间: 精确的锁定范围定义

❌ 缺点:

  • 并发度低: 阻塞较大范围的插入
  • 仅 MySQL: InnoDB 特有机制
  • 性能影响: 高并发场景性能下降
  • 理解复杂: 锁定范围较难准确判断

适用场景 ​

  • REPEATABLE READ 隔离级别: MySQL 默认隔离级别
  • 范围查询: 需要防止幻读的场景
  • 一致性要求高: 确保事务内数据一致性
  • 默认场景: InnoDB 的所有当前读操作

🔧 工作原理 ​

锁定范围:左开右闭区间 ​

临键锁的锁定范围是左开右闭区间 (gap, record]:

索引记录: Record(1) --- Record(3) --- Record(5) --- Record(7)

临键锁范围:
- 第一个临键锁:(-∞, 1]
- 第二个临键锁:(1, 3]
- 第三个临键锁:(3, 5]
- 第四个临键锁:(5, 7]
- 第五个临键锁:(7, +∞)

解读:

  • (1, 3] 表示:锁定大于 1 且小于等于 3 的范围
  • 包括记录 3 本身(记录锁)
  • 包括间隙 (1, 3)(间隙锁)

示例说明 ​

sql
-- 假设表中有 id = 1, 3, 5, 7 的记录

-- 执行范围查询
SELECT * FROM users WHERE id BETWEEN 3 AND 5 FOR UPDATE;

InnoDB 的锁定行为:

锁类型锁定范围说明
临键锁 1(1, 3]锁定记录 3 + 间隙 (1, 3)
临键锁 2(3, 5]锁定记录 5 + 间隙 (3, 5)
临键锁 3(5, 7]锁定间隙 (5, 7),防止插入

效果:

  • ✅ 其他事务不能修改 id=3 或 id=5 的记录
  • ✅ 其他事务不能插入 id 在 (1, 7] 范围内的新记录
  • ❌ 其他事务可以插入 id < 1 或 id > 7 的记录

💻 MySQL InnoDB 实现 ​

基本用法 ​

sql
-- 设置隔离级别为 REPEATABLE READ(默认)
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

-- 范围查询自动使用临键锁
BEGIN;
SELECT * FROM users WHERE id BETWEEN 3 AND 7 FOR UPDATE;
-- 锁定:
-- - 记录 3, 5, 7(记录锁)
-- - 间隙 (1, 3), (3, 5), (5, 7), (7, +∞)(间隙锁)
COMMIT;

-- 等值查询也使用临键锁(非唯一索引)
SELECT * FROM users WHERE age = 18 FOR UPDATE;
-- 锁定所有 age=18 的记录及其前后的间隙

查看临键锁 ​

sql
-- 查看临键锁
SELECT 
    lock_id,
    lock_trx_id,
    lock_mode,
    lock_type,
    lock_table,
    lock_index,
    lock_data
FROM performance_schema.data_locks
WHERE lock_type = 'RECORD';

输出示例:

lock_mode       | lock_data
----------------|----------
X               | 3
X,GAP           | 5

解读:

  • 第一行:记录 3 上的排他锁(记录锁部分)
  • 第二行:记录 5 之前的间隙上的排他锁(间隙锁部分)
  • 合起来就是临键锁

📊 临键锁 vs 记录锁 vs 间隙锁 ​

对比表 ​

特性记录锁间隙锁临键锁
锁定记录✅❌✅
锁定间隙❌✅✅
防止幻读❌✅✅
并发度高中低
隔离级别所有RRRR

关系图 ​

临键锁 = 记录锁 + 间隙锁

例如:锁定 (3, 5]
- 记录锁部分:锁定记录 5
- 间隙锁部分:锁定间隙 (3, 5)

🎯 最佳实践 ​

✅ 推荐做法 ​

1. 使用唯一索引避免不必要的临键锁 ​

sql
-- ✅ 创建唯一索引
CREATE UNIQUE INDEX idx_unique_id ON users(id);

-- 唯一索引的等值查询只加记录锁,不加临键锁
SELECT * FROM users WHERE id = 3 FOR UPDATE;
-- 只锁定记录 3,不锁定间隙

2. 选择合适的隔离级别 ​

sql
-- 如果不需要防止幻读,使用 READ COMMITTED
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- READ COMMITTED 级别下只使用记录锁,不使用临键锁

3. 优化查询减少锁定范围 ​

sql
-- ❌ 不推荐:大范围查询
SELECT * FROM users WHERE id BETWEEN 1 AND 1000 FOR UPDATE;

-- ✅ 推荐:缩小范围
SELECT * FROM users WHERE id BETWEEN 1 AND 10 FOR UPDATE;

❌ 避免陷阱 ​

1. 避免临键锁导致的插入阻塞 ​

sql
-- 事务 A
BEGIN;
SELECT * FROM users WHERE id BETWEEN 10 AND 20 FOR UPDATE;
-- 锁定了临键锁 (10, 20] 和 (20, next_record]

-- 事务 B
INSERT INTO users VALUES (15, 'john');
-- ❌ 被阻塞!15 在临键锁范围内

-- 解决:等待事务 A 提交,或使用 READ COMMITTED

2. 注意临键锁的死锁风险 ​

sql
-- 事务 A
BEGIN;
SELECT * FROM users WHERE id = 5 FOR UPDATE;  -- 临键锁 (3, 5]
INSERT INTO users VALUES (4, 'test');  -- 尝试插入到 (3, 5)

-- 事务 B
BEGIN;
SELECT * FROM users WHERE id = 3 FOR UPDATE;  -- 临键锁 (1, 3]
INSERT INTO users VALUES (4, 'test');  -- 尝试插入到 (3, 5)

-- 结果:可能死锁

🔍 监控与诊断 ​

sql
-- 查看临键锁
SELECT 
    l.lock_id,
    l.lock_mode,
    l.lock_index,
    l.lock_data,
    t.trx_id,
    t.trx_state
FROM performance_schema.data_locks l
JOIN information_schema.innodb_trx t ON l.lock_trx_id = t.trx_id
WHERE l.lock_type = 'RECORD'
ORDER BY t.trx_started;

-- 查看锁等待
SELECT * FROM performance_schema.data_lock_waits;

🚨 常见问题 ​

Q1: 临键锁和间隙锁的区别? ​

答:

  • 间隙锁: 只锁定间隙,不锁定记录
  • 临键锁: 既锁定记录,又锁定间隙
  • 关系: 临键锁 = 记录锁 + 间隙锁

Q2: 如何禁用临键锁? ​

答:

  1. 使用 READ COMMITTED 隔离级别
sql
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
  1. 使用唯一索引的等值查询
sql
SELECT * FROM users WHERE id = 3 FOR UPDATE;  -- id 是唯一索引

Q3: 临键锁的性能影响大吗? ​

答:

影响较大,特别是:

  • 高并发插入场景
  • 大范围查询
  • 长事务持有锁

优化建议:

  • 使用 READ COMMITTED
  • 优化查询减少范围
  • 缩短事务时间

📚 相关资源 ​

内部链接 ​

外部资源 ​


最后更新: 2026-04-12
维护状态: ✅ 完整

Released under MIT License.