应捕获错误码1213并实现指数退避重试,统一多表更新顺序,按主键升序加锁,用INSERT...ON DUPLICATE KEY UPDATE替代先查后改,避免事务中耗时操作,必要时用存储过程封装重试逻辑。
Deadlock found when trying to get lock 怎么办MySQL 在高并发下出现死锁是常态,不是配置错误或代码 bug
的直接证据。InnoDB 会主动检测并回滚其中一方事务,抛出这个错误——关键不是“避免死锁”,而是“快速重试 + 减少冲突面”。
1213 错误码(ER_LOCK_DEADLOCK),不能当作普通异常吞掉或记录后忽略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 中手动开启事务但异常分支遗漏回滚innodb_lock_wait_timeout 是饮鸩止渴;更有效的是缩小事务粒度,例如把“批量更新 1000 行”拆成每 100 行一个事务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 和慢查询日志里锁等待链,而不是立刻调参数。