Skip to content

行锁会出现死锁,根本原因是:行锁的粒度细、锁数量多,导致多个事务可以“交叉”持有不同行的锁,从而形成循环等待。下面分步骤拆解:


1️⃣ 行锁的粒度细,锁数量多

  • 表锁:整个表只有1把锁,所有事务按顺序排队,无法形成循环等待。
  • 行锁:每行记录都可独立加锁(如SELECT ... FOR UPDATE锁定单行)。
    • 事务A可以锁住行1,事务B可以锁住行2,此时两把锁独立存在。
    • 如果后续事务A想再锁行2,事务B想再锁行1,就会互相阻塞。

2️⃣ 死锁的四个必要条件(行锁如何满足)

死锁条件行锁场景下的体现
互斥行锁是独占的(如X锁),同一行只能被一个事务持有。
占有且等待事务A已锁住行1,等待行2;事务B已锁住行2,等待行1。
不可抢占行锁不会强制剥夺,必须等待事务主动释放。
循环等待形成闭环:A等B → B等A(如下图)。

3️⃣ 经典死锁场景示例

场景:两个事务更新同一表的不同行

sql
-- 事务A
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- 锁住行1
UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- 等待行2的锁(被事务B持有)

-- 事务B(几乎同时执行)
BEGIN;
UPDATE accounts SET balance = balance - 50 WHERE id = 2;  -- 锁住行2
UPDATE accounts SET balance = balance + 50 WHERE id = 1;  -- 等待行1的锁(被事务A持有)

结果:事务A和B互相等待对方的锁,形成死锁(循环等待)。
数据库处理:InnoDB会检测到死锁,强制回滚其中一个事务(通常选择代价更小的事务)。


4️⃣ 为什么表锁不会死锁?

  • 表锁只有一把锁,所有事务按顺序排队,无法形成“交叉持有”的循环等待(如事务A和B不可能同时持有表锁的一部分)。

5️⃣ 如何减少行锁死锁?

  • 固定顺序加锁:所有事务按相同顺序访问行(如始终按id升序更新)。
  • 减少锁范围:用WHERE精准命中索引,避免间隙锁扩大锁范围。
  • 降低隔离级别:如从REPEATABLE READ改为READ COMMITTED(减少间隙锁)。
  • 缩短事务:尽快提交/回滚,减少锁持有时间。

总结一句话

行锁因“多把锁+交叉等待”而可能死锁,表锁因“只有一把锁+顺序排队”天然免疫死锁。