数据库死锁

返回 面试 · 速查全集 数据库常见面试题

两个或多个事务互相等待对方持有的锁,谁都无法继续。InnoDB 自动检测并回滚其中一个。

操作系统线程死锁死锁)概念类似,但发生在 DB 锁(行锁/间隙锁) 层面。

详解 死锁事务与锁


典型场景

事务 T1                    事务 T2
UPDATE account SET ...     UPDATE account SET ...
WHERE id = 1;  -- 锁 id=1   WHERE id = 2;  -- 锁 id=2
       │                            │
UPDATE ... WHERE id = 2;    UPDATE ... WHERE id = 1;
       │ 等 T2 释放 id=2            │ 等 T1 释放 id=1
       └────────── 循环等待 ──────────┘

加锁顺序不一致是最常见原因:T1 先 1 后 2,T2 先 2 后 1。


InnoDB 如何处理

  1. 死锁检测:锁等待图有环 → 选代价较小的事务回滚
  2. 客户端收到:ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
  3. 另一事务继续执行

不会无限等待;也不会两个都回滚。


与 OS 死锁对比

维度数据库死锁线程/OS 死锁
等待资源行锁、间隙锁、表锁互斥量、文件、CPU
检测方InnoDB 引擎OS / JVM 工具
处理回滚一个事务需预防/检测/人工 kill
应用层捕获 1213 重试避免嵌套锁、固定顺序

预防手段

手段说明
固定加锁顺序多行更新按 id 排序再更新
缩短事务事务内只做 DB;HTTP/MQ 放事务外
小批量大 UPDATE 拆批,减少锁范围与时间
合适索引无索引导致锁升级为表锁或锁大量间隙
降低隔离级别间隙锁减少(需权衡幻读,慎用)
乐观锁UPDATE ... WHERE id=? AND version=?,减少互斥锁等待
// 应用层:死锁重试(示例)
@Retryable(retryFor = DeadlockLoserDataAccessException.class, maxAttempts = 3)
@Transactional
public void transfer(Long from, Long to, BigDecimal amount) { ... }

排查

-- 查看最近一次死锁信息(InnoDB)
SHOW ENGINE INNODB STATUS\G
-- 关注 LATEST DETECTED DEADLOCK 段:两个事务各自持有的锁与等待的锁

结合业务日志看:哪两张表、哪两个 SQL、是否顺序相反


常见面试题

Q:死锁和锁等待超时区别?
A:死锁是环路,InnoDB 主动回滚一方;锁等待是等别人释放,超时抛 Lock wait timeout exceeded(默认 50s),不一定有环。

Q:间隙锁为什么容易死锁?
A:范围条件锁定多个间隙,与另一事务插入/更新重叠时,等待链更复杂;RR 下更常见。

Q:如何设计转账避免死锁?
A:按账户 id 从小到大依次加锁;或单账户串行队列。

Q:Spring 事务里发生死锁会回滚吗?
A:被回滚的事务会触发 Spring 回滚;捕获异常后可根据 1213 重试整个 @Transactional 方法。


相关笔记