两阶段锁、间隙锁与死锁治理

kp-016 · 事务与并发控制核心约 25 分钟已校对

前置知识点

前置:

相关知识点

相关:

学习进度:

一句话定义

两阶段锁(2PL,Two-Phase Locking)规定事务必须先完成全部加锁再统一放锁,以此换取可串行化;其代价是死锁(Deadlock)成为常态事件,需要检测算法与工程治理。

为什么重要

前置知识

kp-015(隔离级别与并发异常)。

核心概念

原理与机制

2PL 证明可串行化的直觉:若两事务访问冲突项的加锁区间不重叠,则它们在真实时间上必然一先一后,可串行化序即加锁序。严格 2PL 把锁持有到 COMMIT,保证不出现级联回滚(读到的数据必是已提交的)。死锁检测在 InnoDB 中是活跃的:每次加锁等待都检查等待图,发现环即回滚代价小的事务并返回错误 1213,应用必须重试。间隙锁只存在于 MySQL RR:SELECT ... FOR UPDATE WHERE id BETWEEN 10 AND 20 不仅锁住存在的行,还锁住 (10,20) 的空隙,其他事务无法在该区间 INSERT——这就是幻读被抑制的机制,也是两个事务先后锁相邻区间再互相插入时的经典死锁模板。锁的获取顺序纪律(按固定顺序访问资源、事务尽量短、一次锁定所需全部资源)是预防死锁的三板斧。

实例或案例

经典 AB-BA 死锁:

-- T1: UPDATE account SET ... WHERE user_id = 1;  再 WHERE user_id = 2;
-- T2: UPDATE account SET ... WHERE user_id = 2;  再 WHERE user_id = 1;
-- 两事务各持一锁互等 → InnoDB 检测到环,回滚代价小者(错误 1213)

治理:统一所有代码路径按 user_id 升序加锁(T2 改为先 1 后 2),死锁即刻消失。批量导入场景同理:多线程按主键排序后加锁,避免乱序交叉。

常见误区

自测题

  1. 为什么严格 2PL 能避免级联回滚? 答:X 锁持有到提交意味着他人读到的修改必然来自已提交事务,任何事务回滚都不会牵连已读取它数据的事务。
  2. 间隙锁在 RC 级下存在吗? 答:MySQL InnoDB 在 RC 下基本不使用间隙锁(外键与唯一性检查除外),这是 RC 并发更好、但幻读回归的原因。

公式或模型

死锁判定:等待图 G=(V,E),V 为事务、E 为等待边;存在环 ⟺ 死锁。检测复杂度 O(V+E),InnoDB 在锁等待时增量执行。

图示

T1 持有行 1 T2 持有行 2 T1 请求行 2(等待) T2 请求行 1(等待)
AB-BA 死锁:两条等待边构成环,检测器回滚牺牲者后环解除

直观类比

两阶段锁像搬家规则:所有纸箱(资源)都要在出门前一次性封好钥匙、路上不再开箱拿东西;若两口子各拿一把钥匙却都需要对方手里的箱子,就僵住了——按「卧室→客厅→厨房」固定顺序装箱,永远不会僵持。

与其他知识点的关系

kp-017 是避免读锁的乐观路线;kp-015 的幻读抑制依赖间隙锁;kp-022 的 latch(内存闩锁)与事务锁是不同层次的并发结构。

延伸阅读