死锁是MySQL并发资源争抢的必然结果,源于事务循环等待行锁;RR隔离级别下间隙锁加剧死锁,事务越长、锁序不一致、无索引查询等均会显著提升死锁概率。

死锁不是MySQL坏了,是并发资源争抢的必然结果
MySQL(InnoDB)在高并发下频繁死锁,根本原因不是配置错或版本低,而是事务在争夺行锁时形成了循环等待。InnoDB 每次检测到死锁都会自动回滚一个事务并抛出 Deadlock found when trying to get lock,这不是故障告警,而是保护机制生效——它说明系统正在按设计工作,只是你的事务逻辑或索引结构没跟上并发节奏。
RR 隔离级别 + 间隙锁 = 死锁温床
MySQL 默认隔离级别 REPEATABLE READ 在 RR 下会启用 Next-Key Lock(记录锁 + 间隙锁),这对一致性读很友好,但对写操作极其敏感:
- 范围查询(如
WHERE age BETWEEN 20 AND 30)会锁住整个区间,哪怕只更新其中一行 - 无索引字段查询(如
WHERE category_id = 5但category_id未建索引)→ 全表扫描 → 所有扫描行加X锁 + 相邻间隙加GAP锁 - 唯一索引等值查询(
WHERE id = 100)只加Record Lock,安全;但非唯一索引(如status)即使等值查也会触发Gap Lock
这些锁行为在单事务里没问题,一旦多个事务交叉执行、顺序不一致,立刻形成“你等我、我等你”的闭环。
事务越长,死锁概率指数级上升
一个事务从 START TRANSACTION 到 COMMIT 的时间,直接决定锁持有时长。常见放大死锁的操作包括:
- 在事务中调用外部 HTTP 接口(比如通知风控、发短信),几秒响应就把行锁挂起
- 事务内做复杂计算或循环处理,延迟提交
- 批量更新未排序 ID:比如
SELECT ... FOR UPDATE WHERE id IN (9,1,5),InnoDB 实际按1,5,9顺序加锁;若另一事务用(5,9,1),就极易逆序冲突 - ORM 自动生成 SQL 时 where 条件顺序不固定(如 GORM 对 map 参数遍历无序),导致不同请求锁序天然不一致
READ COMMITTED 能降死锁,但不是万能解药
把隔离级别降到 READ COMMITTED 确实能关闭间隙锁(GAP 锁),从而大幅减少因范围锁引发的死锁。但它带来两个硬代价:
- 幻读风险:同一事务中两次
SELECT可能读到新插入的行,业务逻辑若依赖“快照一致性”,就得在应用层补漏 - 部分场景反而更易锁冲突:RC 下虽然不加 GAP 锁,但
UPDATE仍需先SELECT ... FOR UPDATE定位行,若定位不准(比如没走索引),照样全表扫+全表 X 锁
真正有效的做法不是一刀切换隔离级别,而是:先用 SHOW ENGINE INNODB STATUS\G 抓最近死锁详情,看清楚哪条 SQL、哪个索引、什么锁类型(X/S/GAP)在打架,再针对性优化——比如给 WHERE 字段补索引,或强制 ID 列表排序后拼 IN 子句。


















