17370845950

mysql并发事务提交失败怎么办_mysql异常处理方案
应捕获错误码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 UPDATEUPDATE 涉及的行尽量按主键升序锁定,可用 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 后忘记 COMMITROLLBACK,尤其注意 ORM 中手动开启事务但异常分支遗漏回滚
  • 避免在事务中做耗时操作(如 HTTP 请求、文件读写、复杂计算),应拆到事务外
  • 临时调大 innodb_lock_wait_timeout 是饮鸩止渴;更有效的是缩小事务粒度,例如把“批量更新 1000 行”拆成每 100 行一个事务

如何让 MySQL 自动重试失败事务(不依赖应用代码)

MySQL 本身不提供自动重试机制,但可通过存储过程封装基础逻辑,把重试控制权留在数据库侧。适用于简单、固定模式的写操作(如计数器更新、状态流转)。

  • DECLARE EXIT HANDLER FOR 1213, 1205 捕获死锁与锁超时
  • 配合 REPEAT ... UNTIL 实现最多 3 次尝试,每次 ROLLBACKDO 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 是原子操作,只加一次锁,天然规避“查-改”间隙
  • 要求目标字段有 UNIQUEPRIMARY KEY 约束,否则无法触发冲突逻辑
  • 注意 LAST_INSERT_ID() 在冲突时仍会返回插入行的 ID(不是更新行),若业务依赖此值需额外判断 ROW_COUNT()
  • 不要滥用:如果更新逻辑复杂(比如要基于旧值做条件计算),仍需显式事务控制,但可考虑用 SELECT ... FOR UPDATE 加锁单行再更新,比全表扫描安全

真正难处理的从来不是报什么错,而是事务边界是否清晰、SQL 是否可预测、重试是否幂等。线上一旦出现批量提交失败,优先看 INNODB_TRX 和慢查询日志里锁等待链,而不是立刻调参数。