Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > mysql间隙锁与排他锁

MySQL间隙锁与排他锁的区别全解析

作者:伐尘

本文给大家介绍了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;

特点:

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,+∞)

特点:

🎯 实际工作场景

场景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;

📝 总结要点

  1. 排他锁是"行锁" - 锁已存在的数据
  2. 间隙锁是"范围锁" - 锁不存在的间隙
  3. MySQL 默认使用 临键锁(行锁+间隙锁)
  4. 间隙锁主要在 RR隔离级别 防止幻读
  5. 设计好索引可以大大减少不必要的间隙锁

在你的学生表例子中,如果 uid 是唯一索引,SELECT ... WHERE uid=8 FOR UPDATE 只会加行锁。如果 name 不是唯一索引,相同的查询可能会加间隙锁。

到此这篇关于MySQL间隙锁与排他锁的区别全解析的文章就介绍到这了,更多相关mysql间隙锁与排他锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文