锁机制与死锁¶
核心问题:间隙锁是什么?死锁是如何产生的?如何避免?
它解决了什么问题?¶
MVCC 解决了读写冲突,但写写冲突仍需要锁来解决。理解锁机制能帮你: - 排查线上死锁报警 - 理解为什么 RR 隔离级别下某些操作会锁住大范围 - 在高并发写入场景做出正确的隔离级别选择
锁的分类¶
flowchart TD
Lock[MySQL 锁] --> TableLock[表级锁]
Lock --> RowLock[行级锁]
TableLock --> TL1[表共享锁 S\nSELECT ... LOCK IN SHARE MODE]
TableLock --> TL2[表排他锁 X\nSELECT ... FOR UPDATE]
TableLock --> TL3[意向锁 IS/IX\n行锁前自动加,表示意图]
RowLock --> RL1[记录锁 Record Lock\n锁定具体行]
RowLock --> RL2[间隙锁 Gap Lock\n锁定索引间隙,防止插入]
RowLock --> RL3[临键锁 Next-Key Lock\n记录锁+间隙锁,RR默认]
间隙锁详解¶
为什么需要间隙锁? 防止幻读——在 RR 隔离级别下,如果只锁定已有行,其他事务仍可在间隙中插入新行,导致同一事务两次查询结果不同。
-- 假设表中有 id: 1, 5, 10, 15
-- 执行以下查询(RR 隔离级别)
SELECT * FROM t WHERE id BETWEEN 5 AND 10 FOR UPDATE;
-- 加锁范围(临键锁):
-- (1, 5] + (5, 10] = 锁定 id 在 (1, 10] 范围
-- 其他事务无法在此范围内插入 id = 3、7、8 等值
为什么间隙锁只在 RR 级别存在:RC 级别不需要防止幻读(允许幻读),所以没有间隙锁,并发性更高。这也是为什么高并发写入场景有时会把隔离级别降到 RC——减少间隙锁的范围,降低死锁概率。
工作中的坑:间隙锁锁住大范围¶
-- 表中 id 只有 1, 5, 10
-- ⚠️ 以下语句在 RR 下会锁住 (5, +∞) 的间隙!
SELECT * FROM t WHERE id > 5 FOR UPDATE;
-- 原因:RR 级别下,范围查询会加临键锁,锁住查询范围内的所有间隙
-- 导致其他事务无法插入 id=6、7、8... 的数据,引发大量锁等待
死锁产生与检测¶
死锁产生的四个必要条件:互斥、占有并等待、不可剥夺、循环等待
sequenceDiagram
participant T1 as 事务1
participant T2 as 事务2
T1->>T1: 锁定 id=1 的行
T2->>T2: 锁定 id=2 的行
T1->>T2: 请求锁定 id=2(等待)
T2->>T1: 请求锁定 id=1(等待)
Note over T1,T2: 死锁!InnoDB 检测到循环等待
Note over T1,T2: 自动回滚 undo log 量较小的事务
InnoDB 死锁检测:InnoDB 有内置的死锁检测机制,检测到死锁后会自动回滚其中一个事务(通常是 undo log 量较小的那个),并返回错误 ERROR 1213: Deadlock found。
为什么回滚 undo log 量小的事务:回滚代价最小,对业务影响最小。被回滚的事务可以重试,而代价大的事务已经做了很多工作,重试成本更高。
死锁排查¶
-- 查看最近一次死锁信息
SHOW ENGINE INNODB STATUS;
-- 输出中找到 LATEST DETECTED DEADLOCK 部分
-- 可以看到:
-- 1. 哪两个事务发生了死锁
-- 2. 各自持有的锁和等待的锁
-- 3. 哪个事务被回滚
避免死锁的实践¶
| 实践 | 说明 |
|---|---|
| 固定加锁顺序 | 所有事务按相同顺序访问资源,打破循环等待 |
| 缩短事务 | 减少锁持有时间,降低死锁概率 |
| 避免大事务 | 拆分为小事务,减少锁的范围和持有时间 |
| 精确查询条件 | SELECT ... FOR UPDATE 时尽量精确条件,减少间隙锁范围 |
| 降低隔离级别 | 高并发写入场景考虑用 RC,消除间隙锁 |
面试高频问题¶
Q:间隙锁是什么?为什么需要间隙锁?
间隙锁锁定索引间隙(不存在的区间),防止其他事务在间隙中插入数据,从而防止幻读。只在 RR 隔离级别下存在,RC 级别没有间隙锁。
Q:如何排查死锁?如何避免死锁?
排查:
SHOW ENGINE INNODB STATUS查看最近一次死锁信息。避免:保持固定加锁顺序、缩短事务、减少锁范围、避免大事务。
Q:为什么高并发写入场景有时会把隔离级别降到 RC?
RC 级别没有间隙锁,锁的范围更小,死锁概率更低,并发性更高。代价是允许幻读,但很多业务场景可以接受。