MySQL间隙锁与排他锁的区别全解析
【MySQL】间隙锁 与 排他锁 的区别
这是一个非常核心的数据库锁机制问题。简单来说:排他锁(X锁)是"锁什么",间隙锁(Gap Lock)是"锁哪里"。
📊 核心区别对比表
| 特性 | 排他锁 (X锁) | 间隙锁 (Gap Lock) |
|---|---|---|
| 锁的类型 | 锁类型 - 定义锁的"权限" | 锁范围 - 定义锁的"位置" |
| 作用对象 | 已存在的数据行 | 数据行之间的间隙 |
| 兼容性 | 与任何其他锁都不兼容 | 与其他间隙锁兼容,但与插入意向锁冲突 |
| 主要目的 | 防止其他事务读写数据 | 防止其他事务在范围内插入新数据 |
| 可见范围 | 行记录本身 | 索引记录之间的"空隙" |
| 何时使用 | UPDATE、DELETE、SELECT…FOR UPDATE | 可重复读隔离级别下的范围查询 |
🔍 详细解释
1. 排他锁(X锁)- 行级锁
-- 示例:对uid=8的行加排他锁 SELECT * FROM student WHERE uid = 8 FOR UPDATE; -- 或 UPDATE student SET age = 60 WHERE uid = 8;
特点:
- 锁定具体的数据行
- 其他事务不能读取(SELECT…FOR UPDATE)也不能修改(UPDATE/DELETE)该行
- 可以看作"写锁"
2. 间隙锁(Gap Lock)- 范围锁
-- 示例:锁定uid在(5, 10)之间的所有间隙 SELECT * FROM student WHERE uid BETWEEN 6 AND 9 FOR UPDATE; -- 或 SELECT * FROM student WHERE uid > 5 AND uid < 10 FOR UPDATE;
假设你的表数据:
uid: 1, 2, 3, 4, 5, 7, 8, 10, 11
间隙包括:(-∞,1), (1,2), (2,3), …, (5,7), (7,8), (8,10), (10,11), (11,+∞)
特点:
- 锁定索引记录之间的空隙,而不是记录本身
- 防止其他事务在范围内插入新数据
- 只在可重复读(REPEATABLE-READ) 隔离级别下有效
🎯 实际工作场景
场景1:防止幻读(Phantom Read)
-- 事务一
BEGIN;
SELECT * FROM student WHERE age BETWEEN 20 AND 30 FOR UPDATE;
-- 锁定所有age在20-30之间的间隙
-- 事务二尝试插入age=25的新记录(会被阻塞)
INSERT INTO student (name, age, sex) VALUES ('new_student', 25, 'M');场景2:组合使用 - 临键锁(Next-Key Lock)
MySQL 实际上经常使用 **临键锁 = 记录锁 + 间隙锁** -- 锁定uid=8的行及其前后的间隙 SELECT * FROM student WHERE uid = 8 FOR UPDATE; -- 实际锁定:(7, 8] 和 (8, 10)
⚠️ 重要注意事项
1. 间隙锁只作用于非唯一索引
-- 如果name有唯一索引,不会加间隙锁 SELECT * FROM student WHERE name = 'bbbb' FOR UPDATE; -- 只对'bbbb'这一行加排他锁 -- 如果name没有索引或不是唯一索引,可能会锁全表间隙 SELECT * FROM student WHERE name LIKE 'b%' FOR UPDATE;
2. 查看锁信息
-- 查看当前的锁(MySQL 8.0+)
SELECT
engine_transaction_id as trx_id,
lock_type,
lock_mode,
lock_data,
index_name
FROM performance_schema.data_locks
WHERE object_name = 'student';3. 死锁风险
间隙锁容易导致死锁,特别是并发插入时:
-- 事务A BEGIN; SELECT * FROM student WHERE uid = 15 FOR UPDATE; -- 间隙锁(10, +∞) -- 事务B BEGIN; SELECT * FROM student WHERE uid = 16 FOR UPDATE; -- 间隙锁(10, +∞) -- 事务A INSERT INTO student (uid, name) VALUES (15, 'test'); -- 等待B -- 事务B INSERT INTO student (uid, name) VALUES (16, 'test'); -- 等待A → 死锁!
🔧 性能影响与优化
| 情况 | 影响 | 解决方案 |
|---|---|---|
| 全表间隙锁 | 锁住所有间隙,并发极差 | 使用唯一索引查询 |
| 范围查询 | 锁住大范围间隙 | 缩小查询范围 |
| 批量操作 | 可能锁大量间隙 | 分批次操作 |
| 高并发插入 | 容易死锁 | 降低隔离级别为RC |
💡 最佳实践建议
明确使用场景:
需要防止幻读 → 使用间隙锁(RR隔离级别)
只需要防止数据修改 → 使用行级排他锁
索引设计:
-- 好的设计:使用唯一索引避免不必要的间隙锁 CREATE UNIQUE INDEX idx_uid ON student(uid); -- 查询时使用唯一索引 SELECT * FROM student WHERE uid = 8 FOR UPDATE; -- 只锁一行
隔离级别选择:
-- 如果不需要防止幻读,使用读已提交 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; -- 此时不会加间隙锁
避免长事务:
-- 尽快提交事务,减少锁持有时间 BEGIN; -- 立即执行需要加锁的操作 SELECT ... FOR UPDATE; UPDATE ...; -- 尽快提交 COMMIT;
📝 总结要点
- 排他锁是"行锁" - 锁已存在的数据
- 间隙锁是"范围锁" - 锁不存在的间隙
- MySQL 默认使用 临键锁(行锁+间隙锁)
- 间隙锁主要在 RR隔离级别 防止幻读
- 设计好索引可以大大减少不必要的间隙锁
在你的学生表例子中,如果 uid 是唯一索引,SELECT ... WHERE uid=8 FOR UPDATE 只会加行锁。如果 name 不是唯一索引,相同的查询可能会加间隙锁。
到此这篇关于MySQL间隙锁与排他锁的区别全解析的文章就介绍到这了,更多相关mysql间隙锁与排他锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
mysql 5.7.18 安装配置方法图文教程(CentOS7)
这篇文章主要为大家详细介绍了CentOS 7下mysql 5.7.18 安装配置方法图文教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下2017-04-04


最新评论