mysql死锁是事务间资源等待形成闭环的系统级阻塞,需通过innodb_status定位,结合隔离级别、next-key lock、索引与执行计划分析,并以固定更新顺序、缩短事务、合理建索引等策略规避。

MySQL 死锁不是“谁先抢到锁谁赢”的简单问题,而是事务间资源等待形成闭环导致的系统级阻塞。面试中常考的不是定义,而是你能否快速定位、复现、分析并规避——核心看对事务隔离级别、加锁机制(尤其是 Next-Key Lock)、SQL 执行计划与索引结构的理解是否扎实。
一、典型死锁场景:转账操作中的双向更新
两个事务同时执行跨账户转账,且更新顺序相反:
- T1 执行:UPDATE accounts SET balance = balance - 100 WHERE id = 1;(持 id=1 的行锁)
- T2 执行:UPDATE accounts SET balance = balance - 100 WHERE id = 2;(持 id=2 的行锁)
- T1 接着执行:UPDATE accounts SET balance = balance + 100 WHERE id = 2;(等待 T2 释放 id=2)
- T2 接着执行:UPDATE accounts SET balance = balance + 100 WHERE id = 1;(等待 T1 释放 id=1)
→ 形成 A→B、B→A 的等待环,InnoDB 检测后回滚其中一个事务(通常选代价小的那个),抛出 Deadlock found when trying to get lock。
二、隐藏更深的死锁:唯一索引+INSERT ... ON DUPLICATE KEY UPDATE
表面看是“插入或更新”,但 InnoDB 在遇到重复键时会先加 duplicate key lock(一种间隙锁变体),再尝试更新。若两个事务并发插入同一主键/唯一键值:
- T1 和 T2 同时执行:INSERT INTO users(id, name) VALUES(100, 'A') ON DUPLICATE KEY UPDATE name = 'A';
- InnoDB 为避免幻读,在唯一索引上加 S 锁(共享锁)于该记录(或间隙),而 UPDATE 阶段又需 X 锁 → 可能触发锁升级冲突
- 尤其在 RC 隔离级别下不加间隙锁,但唯一索引的 duplicate key 检查仍会加特殊锁,容易被忽略
三、如何快速定位和分析?关键看 innodb_status
发生死锁后,执行 SHOW ENGINE INNODB STATUS\G,重点关注 LATEST DETECTED DEADLOCK 区块:
- 看 *** (1) TRANSACTION 和 *** (2) TRANSACTION 的事务 ID、运行时间、持有锁(HELD LOCKS)、等待锁(WAITING FOR THIS LOCK TO BE GRANTED)
- 比对 SQL 语句,确认是否因索引失效(如 where 条件未走索引,升级为表锁)、范围查询(如
WHERE age > 25触发 Next-Key Lock 覆盖多个间隙)引发 - 注意锁类型:
RECORD LOCKS(行锁)、_GAP LOCKS(间隙锁)、NEXT KEY LOCKS(两者合体),它们共同构成死锁温床
四、实用规避策略:从设计到编码
死锁无法完全杜绝,但可大幅降低概率:
- 固定 DML 顺序:所有业务逻辑按相同字段(如 user_id 升序)批量更新,打破循环等待前提
- 缩短事务生命周期:不在事务内做 RPC、文件读写、用户输入等待;把 SELECT 提前,计算完再进事务
- 合理建索引:确保 WHERE / ORDER BY / JOIN 条件都命中索引,避免全表扫描导致锁升级
-
慎用 SELECT ... FOR UPDATE / LOCK IN SHARE MODE:非必要不加锁;如需校验再更新,优先用
INSERT ... ON DUPLICATE KEY UPDATE或REPLACE INTO替代“先查后更” - 应用层重试机制:捕获 Deadlock exception 后,幂等重试 1–2 次(注意避免无限循环)
真正拉开差距的,不是背出“死锁四个必要条件”,而是看到一条报错日志,能立刻反推出哪两条 SQL、在什么数据分布和索引结构下撞出了锁环。多在本地用 begin; ... ; select * from information_schema.INNODB_TRX; 模拟并发,比死记理论管用得多。










