mysql中事务处理中的死锁与解决方案

来源:这里教程网 时间:2026-02-28 20:50:08 作者:

死锁是怎么发生的(以 UPDATE 为例) MySQL 的死锁不是“卡住”,而是两个或多个事务互相等待对方持有的锁,形成循环依赖。最常见于并发执行
UPDATE
时按不同顺序访问同一组行。比如事务 A 先锁
id=1
再试图锁
id=2
,而事务 B 反过来先锁
id=2
再等
id=1
——InnoDB 检测到后会主动回滚其中一个事务,并报错:
Deadlock found when trying to get lock; try restarting transaction
死锁只在 可重复读(REPEATABLE READ) 隔离级别下会被 InnoDB 主动检测并中断
SELECT ... FOR UPDATE
UPDATE
在有索引条件下加的是行级锁;若走全表扫描,可能升级为表锁,大幅增加死锁概率
没有索引的
WHERE
条件会让 MySQL 无法精确定位行,从而对更多无关行加锁,埋下隐患

如何复现和确认死锁日志 死锁发生后,MySQL 不会静默失败,但也不会自动重试。你需要主动查
INFORMATION_SCHEMA.INNODB_TRX
和错误日志,或者开启死锁检测日志: 执行
SHOW ENGINE INNODB STATUS\G
,在输出末尾的
LATEST DETECTED DEADLOCK
区域能看到最近一次死锁的完整参与者、SQL、持锁/等锁状态
确保
innodb_print_all_deadlocks = ON
(写入 error log),避免只靠
SHOW ENGINE
查历史
注意:该日志不记录事务开始时间或应用层上下文,需结合业务日志定位具体代码路径

避免死锁的四个实操原则 核心思路是「消除加锁顺序不确定性」,而非单纯减少事务长度: 所有涉及多行更新的逻辑,固定行处理顺序:例如统一按
ORDER BY id ASC
排序后再更新,避免 A 事务按 id 升序、B 事务按降序操作同一数据集
尽量在事务内 一次性申请所有需要的锁:把多个
UPDATE
合并在一条语句中(如
UPDATE t SET x=1 WHERE id IN (1,2,3)
),而不是分多次单行更新
避免在事务中混用
SELECT ... FOR UPDATE
和普通
SELECT
:前者加锁,后者不加,容易导致后续
UPDATE
因条件变化触发额外锁竞争
更新语句必须走索引:检查
EXPLAIN
输出中的
key
字段是否非 NULL;若显示
NULL
,说明走了全表扫描,应补上对应索引

应用层该怎么安全重试 MySQL 报
Deadlock found when trying to get lock
后,事务已回滚,应用必须捕获该错误并重试——但不能无脑重试: 只对明确知道是死锁的错误码重试:
1213
(MySQL errno),其他错误如
1205
(超时)或主键冲突不应重试
设置最大重试次数(通常 ≤ 3),防止雪崩;每次重试前加随机微小延迟(如 10–100ms),错开竞争窗口 重试时要保证业务幂等:例如用
INSERT ... ON DUPLICATE KEY UPDATE
替代先查后插,或用唯一业务字段做插入校验
不要在存储过程中封装重试逻辑:MySQL 存储过程无法捕获死锁异常并控制重试节奏,必须由应用层处理

死锁无法完全杜绝,但只要加锁顺序一致、索引到位、应用层有兜底重试,就能把影响控制在毫秒级瞬时失败范围内。最容易被忽略的是:开发时用单线程测试看不出问题,一上生产并发量上来,没排序的

IN
列表或缺失索引立刻暴露。

相关推荐