间隙锁 (Gap Lock)
📋 概述
间隙锁(Gap Lock)是 MySQL InnoDB 引擎特有的一种锁算法,它锁定的不是索引记录本身,而是索引记录之间的间隙。间隙锁的主要作用是防止其他事务在间隙中插入新记录,从而避免"幻读"现象。
核心特点
✅ 优点:
- 防止幻读: 阻止其他事务在间隙中插入新记录
- 保证一致性: 确保范围查询的可重复性
- 实现简单: 只需锁定间隙,无需额外数据结构
- 按需启用: 只在 REPEATABLE READ 隔离级别下生效
❌ 缺点:
- 降低并发: 阻塞 INSERT 操作
- 仅 MySQL: 其他数据库使用不同机制
- 可能过度锁定: 即使没有记录也会锁定间隙
- 性能影响: 高并发插入场景性能下降
适用场景
- REPEATABLE READ 隔离级别: MySQL 默认隔离级别
- 范围查询: WHERE id BETWEEN 1 AND 10
- 防止幻读: 需要确保多次查询结果一致
- 唯一索引检查: 检查唯一约束时
🔧 工作原理
间隙的概念
索引结构:
Record(-∞) --- Record(1) --- Record(3) --- Record(5) --- Record(+∞)
| | | | |
间隙1 间隙2 间隙3 间隙4 间隙5间隙定义:
- 间隙1: (-∞, 1) - 小于 1 的范围
- 间隙2: (1, 3) - 1 和 3 之间的范围
- 间隙3: (3, 5) - 3 和 5 之间的范围
- 间隙4: (5, +∞) - 大于 5 的范围
间隙锁的锁定范围
sql
-- 假设表中有 id = 1, 3, 5, 7 的记录
-- 查询 id = 3 的记录并加锁
SELECT * FROM users WHERE id = 3 FOR UPDATE;InnoDB 的锁定行为(REPEATABLE READ 级别):
- 锁定 id=3 的记录(记录锁)
- 锁定间隙 (1, 3)(间隙锁)
- 锁定间隙 (3, 5)(间隙锁)
效果:
- 其他事务不能插入 id 在 (1, 5) 范围内的新记录
- 其他事务可以插入 id < 1 或 id > 5 的记录
纯间隙锁 (Pure Gap Lock)
某些情况下,间隙锁只锁定间隙,不锁定记录:
sql
-- 查询不存在的记录
SELECT * FROM users WHERE id = 4 FOR UPDATE;
-- id=4 不存在,但仍会锁定间隙 (3, 5)作用: 防止其他事务插入 id=4 的记录
💻 MySQL InnoDB 实现
基本用法
sql
-- 设置隔离级别为 REPEATABLE READ
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
-- 范围查询加锁
BEGIN;
SELECT * FROM users WHERE id BETWEEN 1 AND 10 FOR UPDATE;
-- 锁定:
-- 1. id 在 [1, 10] 范围内的所有记录(记录锁)
-- 2. 间隙 (-∞, 1) 的前部分
-- 3. 间隙 (10, +∞) 的前部分
COMMIT;
-- 等值查询(记录不存在)
SELECT * FROM users WHERE id = 4 FOR UPDATE;
-- 锁定间隙 (3, 5),假设存在 id=3 和 id=5查看间隙锁
sql
-- 查看间隙锁(通过 performance_schema)
SELECT
lock_id,
lock_trx_id,
lock_mode,
lock_type,
lock_table,
lock_index,
lock_data
FROM performance_schema.data_locks
WHERE lock_mode LIKE '%GAP%';输出示例:
lock_mode | lock_data
----------------|----------
X,GAP | 3解读:
lock_mode = X,GAP: 排他间隙锁lock_data = 3: 锁定的是 id=3 之后的间隙
📊 间隙锁的影响范围
不同查询类型的间隙锁
1. 等值查询(记录存在)
sql
-- 假设存在 id = 1, 3, 5, 7
SELECT * FROM users WHERE id = 3 FOR UPDATE;锁定范围:
- 记录锁: id=3
- 间隙锁: (1, 3) 和 (3, 5)
2. 等值查询(记录不存在)
sql
SELECT * FROM users WHERE id = 4 FOR UPDATE;锁定范围:
- 纯间隙锁: (3, 5)
3. 范围查询
sql
SELECT * FROM users WHERE id BETWEEN 3 AND 7 FOR UPDATE;锁定范围:
- 记录锁: id=3, 5, 7
- 间隙锁: (1, 3), (3, 5), (5, 7), (7, +∞) 的前部分
4. 唯一索引的等值查询
sql
-- 假设 id 是唯一索引
SELECT * FROM users WHERE id = 3 FOR UPDATE;锁定范围:
- 只锁定记录 id=3(记录锁)
- 不锁定间隙(因为唯一索引不需要防止插入)
注意: 唯一索引的等值查询不会加间隙锁!
🎯 最佳实践
✅ 推荐做法
1. 理解间隙锁的行为
sql
-- ✅ 推荐:使用唯一索引避免不必要的间隙锁
CREATE UNIQUE INDEX idx_unique_id ON users(id);
-- 唯一索引的等值查询不会加间隙锁
SELECT * FROM users WHERE id = 3 FOR UPDATE;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 1 AND 10 FOR UPDATE;
-- 锁定了间隙 (10, +∞) 的前部分
-- 事务 B
INSERT INTO users VALUES (15, 'john');
-- ❌ 被阻塞!虽然 15 不在 [1, 10] 范围内,但在间隙锁范围内
-- 解决:等待事务 A 提交,或使用 READ COMMITTED 隔离级别2. 避免死锁
sql
-- 事务 A
BEGIN;
SELECT * FROM users WHERE id = 5 FOR UPDATE; -- 锁定间隙 (3, 5) 和 (5, 7)
INSERT INTO users VALUES (4, 'test'); -- 尝试在间隙 (3, 5) 插入
-- 事务 B
BEGIN;
SELECT * FROM users WHERE id = 3 FOR UPDATE; -- 锁定间隙 (1, 3) 和 (3, 5)
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,
t.trx_started
FROM performance_schema.data_locks l
JOIN information_schema.innodb_trx t ON l.lock_trx_id = t.trx_id
WHERE l.lock_mode LIKE '%GAP%'
ORDER BY t.trx_started;
-- 查看间隙锁导致的等待
SELECT * FROM performance_schema.data_lock_waits;🚨 常见问题
Q1: 间隙锁只在 MySQL 中存在吗?
答: 是的,间隙锁是 InnoDB 特有的。其他数据库使用不同机制:
- PostgreSQL: 使用谓词锁(Predicate Lock)
- SQL Server: 使用键范围锁(Key-Range Lock)
- Oracle: 依赖 MVCC,不需要间隙锁
Q2: 如何避免间隙锁?
答:
- 使用 READ COMMITTED 隔离级别
sql
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;- 使用唯一索引的等值查询
sql
-- 唯一索引的等值查询不加间隙锁
SELECT * FROM users WHERE id = 3 FOR UPDATE;- 使用快照读
sql
-- 普通 SELECT 不加锁
SELECT * FROM users WHERE id = 3;Q3: 间隙锁会影响性能吗?
答: 会,特别是在高并发插入场景。
影响:
- 阻塞 INSERT 操作
- 增加锁等待时间
- 可能导致死锁
优化建议:
- 使用 READ COMMITTED 隔离级别
- 优化查询减少锁范围
- 批量插入时注意顺序
📚 相关资源
内部链接
外部资源
最后更新: 2026-04-12
维护状态: ✅ 完整