应捕获错误码1213并实现指数退避重试,统一多表更新顺序,按主键升序加锁,用INSERT...ON DUPLICATE KEY UPDATE替代先查后改,避免事务中耗时操作,必要时用存储过程封装重试逻辑。

事务提交时提示 Deadlock found when trying to get lock 怎么办
MySQL 在高并发下出现死锁是常态,不是配置错误或代码 bug 的直接证据。InnoDB 会主动检测并回滚其中一方事务,抛出这个错误——关键不是“避免死锁”,而是“快速重试 + 减少冲突面”。
- 应用层必须捕获
1213错误码(ER_LOCK_DEADLOCK),不能当作普通异常吞掉或记录后忽略 - 重试逻辑建议限制在 3 次以内,每次间隔用指数退避(如 10ms → 30ms → 90ms),避免雪崩式重试
- 检查 SQL 是否存在“多表交叉更新顺序不一致”:比如事务 A 先更新
users再更新orders,事务 B 反过来,就极易死锁 - 把
SELECT ... FOR UPDATE或UPDATE涉及的行尽量按主键升序锁定,可用ORDER BY id ASC+LIMIT控制范围
Lock wait timeout exceeded 超时常见原因和调优点
这不是死锁,而是某条语句等锁太久(默认 50 秒)被强制中断,错误码 1205。本质是锁持有时间过长,或等待队列太深。
- 查当前长事务:
SELECT trx_id, trx_started, trx_state, trx_weight, trx_query FROM information_schema.INNODB_TRX ORDER BY trx_started LIMIT 5;
- 确认是否有未提交的
BEGIN后忘记COMMIT或ROLLBACK,尤其注意 ORM 中手动开启事务但异常分支遗漏回滚 - 避免在事务中做耗时操作(如 HTTP 请求、文件读写、复杂计算),应拆到事务外
- 临时调大
innodb_lock_wait_timeout是饮鸩止渴;更有效的是缩小事务粒度,例如把“批量更新 1000 行”拆成每 100 行一个事务
如何让 MySQL 自动重试失败事务(不依赖应用代码)
MySQL 本身不提供自动重试机制,但可通过存储过程封装基础逻辑,把重试控制权留在数据库侧。适用于简单、固定模式的写操作(如计数器更新、状态流转)。
- 用
DECLARE EXIT HANDLER FOR 1213, 1205捕获死锁与锁超时 - 配合
REPEAT ... UNTIL实现最多 3 次尝试,每次ROLLBACK后DO SLEEP(0.01)避免忙等 - 注意:存储过程中不能嵌套事务,需确保外部没开启事务(否则
START TRANSACTION会报错) - 示例片段(简化版):
DELIMITER $$ CREATE PROCEDURE safe_update_counter(IN p_id INT) BEGIN DECLARE retry_count INT DEFAULT 0; DECLARE max_retries INT DEFAULT 3; DECLARE CONTINUE HANDLER FOR 1213, 1205 BEGIN SET retry_count = retry_count + 1; IF retry_count <= max_retries THEN DO SLEEP(0.01); ITERATE retry_loop; END IF; END; retry_loop: REPEAT START TRANSACTION; UPDATE counters SET value = value + 1 WHERE id = p_id; COMMIT; LEAVE retry_loop; UNTIL retry_count > max_retries END REPEAT; END$$ DELIMITER ;
使用 INSERT ... ON DUPLICATE KEY UPDATE 替代先查后更,减少锁竞争
这是最常被忽视的并发优化手段。很多业务写法是:SELECT ... 判断是否存在 → 不存在则 INSERT,存在则 UPDATE。这中间存在竞态窗口,且两次语句都加锁。
-
ON DUPLICATE KEY UPDATE是原子操作,只加一次锁,天然规避“查-改”间隙 - 要求目标字段有
UNIQUE或PRIMARY KEY约束,否则无法触发冲突逻辑 - 注意
LAST_INSERT_ID()在冲突时仍会返回插入行的 ID(不是更新行),若业务依赖此值需额外判断ROW_COUNT() - 不要滥用:如果更新逻辑复杂(比如要基于旧值做条件计算),仍需显式事务控制,但可考虑用
SELECT ... FOR UPDATE加锁单行再更新,比全表扫描安全
真正难处理的从来不是报什么错,而是事务边界是否清晰、SQL 是否可预测、重试是否幂等。线上一旦出现批量提交失败,优先看 INNODB_TRX 和慢查询日志里锁等待链,而不是立刻调参数。










