mysql并发事务提交失败怎么办_mysql异常处理方案

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

事务提交时提示
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
和慢查询日志里锁等待链,而不是立刻调参数。

相关推荐