死锁是多个事务因争夺资源而互相等待无法推进的状态;InnoDB自动检测并回滚代价最小事务,核心原因是加锁顺序不一致与循环等待,需同时满足互斥、持有并请求、不可剥夺、循环等待四条件。

死锁是指两个或多个事务在执行过程中,因争夺资源而互相等待,导致所有相关事务都无法继续推进的状态。MySQL(特别是 InnoDB 引擎)中一旦发生死锁,系统会自动检测并强制回滚其中一个事务(通常选择代价最小的那个),抛出错误:ERROR 1213 (40001): Deadlock found when trying to get lock。
死锁产生的核心原因
本质是“加锁顺序不一致 + 循环等待”。InnoDB 死锁必须同时满足四个条件:互斥、持有并请求、不可剥夺、循环等待。实际开发中最常见、最可控的诱因有以下几类:
- 锁获取顺序不同:事务 A 先更新行 1 再更新行 2,事务 B 却先更新行 2 再更新行 1。双方各自持有一把锁,又申请对方的锁,形成闭环。
-
索引路径引发隐式锁竞争:比如对非唯一普通索引字段执行
UPDATE ... WHERE name = 'xxx',InnoDB 会先锁name索引项,再通过回表锁主键。若另一事务已持有该主键行的 X 锁,并反向尝试锁name索引,就可能死锁。 -
间隙锁(gap lock)或 next-key 锁参与竞争:范围查询(如
WHERE age BETWEEN 20 AND 30)会锁定索引间隙。多个事务对重叠间隙加锁,且顺序错乱时,容易触发死锁,尤其在可重复读(RR)隔离级别下更常见。 - 长事务 + 高并发:事务运行时间越长,持有锁的时间就越久,与其他事务发生交叉加锁的概率呈指数上升;配合高并发写入,死锁频率显著提高。
哪些操作不会导致 MySQL 死锁?
明确一点:表级锁(如 MyISAM 使用的锁)本身不支持事务,也没有死锁检测机制,所以不会产生死锁。而 InnoDB 的行级锁机制天然具备死锁可能性——这也正是它支持高并发事务的代价之一。
一个典型复现场景
假设有用户表 users(id, name, balance),其中 name 有普通索引:
- 事务 A 执行:
UPDATE users SET balance = balance + 100 WHERE name = 'Alice'; - 事务 B 同时执行:
UPDATE users SET balance = balance - 50 WHERE id = 1001; - 如果事务 A 已拿到
name='Alice'索引锁,正准备回表锁主键id=1001;而事务 B 已持有id=1001的 X 锁,又试图加name索引上的锁(例如执行了SELECT ... FOR UPDATE带name条件),此时就会触发死锁。










