死锁源于多事务循环等待资源,需从索引优化、统一更新顺序、降低隔离级别等维度预防,而非仅依赖检测回滚。

死锁不是单个事务的问题,而是多个事务互相等待资源
MySQL 的死锁检测机制会自动选一个事务回滚(报 Deadlock found when trying to get lock),但如果你频繁遇到死锁,说明业务逻辑或索引设计存在结构性风险——回滚本身会引发重试、延迟、甚至下游超时雪崩。关键不是“怎么捕获死锁”,而是“怎么让事务不进入互相卡住的状态”。
UPDATE/DELETE 必须走索引,否则升级为表级锁
这是最常见也最容易被忽略的死锁诱因:没有 WHERE 条件、或 WHERE 字段没索引,InnoDB 会扫描全表并给所有扫描过的行加 next-key lock,极大增加锁冲突概率。
- 检查执行计划:
EXPLAIN UPDATE t SET x=1 WHERE unindexed_col = 'a'—— 如果type是ALL或index,立刻加索引 - 复合索引要注意最左前缀:WHERE
a = ? AND b = ?可用(a,b)索引;但b = ?单独查询无法利用它 - 字符串字段用
LIKE 'abc%'可走索引,LIKE '%abc'不行,后者也会触发全表扫描加锁
事务内操作顺序不一致,是死锁连锁反应的直接推手
两个事务如果按不同顺序更新同一组行,就极易形成环形等待。比如事务 A 先改 id=1 再改 id=5,事务 B 反过来,哪怕只是毫秒级交错,InnoDB 就可能判定为死锁。
- 统一业务层的更新顺序:对涉及多行的批量操作,强制按主键升序排序后再更新,例如先
ORDER BY id ASC再遍历执行 - 避免在事务中调用外部服务或用户输入——这些不可控延迟会让事务持有锁的时间变长,放大顺序错乱风险
- INSERT ... ON DUPLICATE KEY UPDATE 虽然原子,但如果并发插入相同唯一键值,仍可能因加锁顺序不同引发死锁,需结合唯一索引和预排序缓解
READ COMMITTED 隔离级别能减少间隙锁,但不解决根本逻辑问题
默认的 REPEATABLE READ 会使用 next-key lock(记录锁 + 间隙锁),防止幻读但也扩大了锁范围;改成 READ COMMITTED 后,InnoDB 只加记录锁,间隙锁基本禁用,锁粒度变小,死锁概率下降。
- 修改方式:连接级设
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED,或在应用连接池配置中全局指定 - 代价:可能读到“半提交”状态(其他事务刚提交的数据),业务需确认是否可接受;MVCC 版本链变短,但不会影响一致性读语义
- 注意:即使隔离级别降了,如果 SQL 还是不走索引、或更新顺序混乱,死锁依然会发生——它只是降低了锁冲突面,不是银弹
真正难处理的是那些跨服务、跨数据库、带缓存更新的复合事务。锁粒度再细,也挡不住逻辑上绕不开的资源争抢。这时候得回到代码里看:这一笔更新,是不是非得塞进这个大事务?能不能拆?有没有最终一致性替代方案?











