mysql锁等待超时怎么解决_mysql并发异常处理

锁等待超时错误表现为“ERROR 1205 (40001): Lock wait timeout exceeded”,即事务等待锁超时(默认50秒),非死锁;可通过INNODB_TRX等系统视图定位阻塞源,重点排查Sleep状态但未提交事务的连接;避免方法包括:走索引、缩短事务、拆分批量操作、降隔离级别、慎用FOR UPDATE,而非依赖调高超时参数。

锁等待超时错误长什么样

执行 INSERTUPDATEDELETE 时突然报错:

ERROR 1205 (40001): Deadlock found when trying to get lock; try restarting transaction
或更常见的是:
ERROR 1205 (40001): Lock wait timeout exceeded; try restarting transaction
这说明事务在等某个锁,但等了太久(默认 50 秒),MySQL 主动放弃了。不是死锁,是“干等超时”。

查谁在占着锁不放

先连上 MySQL,用系统视图定位阻塞源头:

SELECT 
  r.trx_id waiting_trx_id,
  r.trx_mysql_thread_id waiting_thread,
  r.trx_query waiting_query,
  b.trx_id blocking_trx_id,
  b.trx_mysql_thread_id blocking_thread,
  b.trx_query blocking_query
FROM information_schema.INNODB_TRX r
INNER JOIN information_schema.INNODB_LOCK_WAITS w ON w.requesting_trx_id = r.trx_id
INNER JOIN information_schema.INNODB_TRX b ON b.trx_id = w.blocking_trx_id;

如果返回空,说明锁已释放或没发生等待;有结果就看 blocking_query 是什么语句,再查对应线程:

SELECT * FROM information_schema.PROCESSLIST WHERE ID = ;
特别留意状态为 Sleep 却持有事务未提交的连接——这是最常见的“幽灵锁源”。

怎么避免锁等太久

根本思路是缩短锁持有时间 + 减少锁冲突概率:

  • 所有写操作尽量走索引,避免全表扫描导致锁住大量无关行;检查 EXPLAIN 输出,确认 type 不是 ALL
  • 事务里只做必要操作,不要在事务中调外部 API、发邮件、等用户输入
  • 批量更新改用 IN 拆小批次(比如每次 100 行),别一次性 UPDATE ... WHERE id IN (1..10000)
  • 读已提交(READ COMMITTED)比可重复读(REPEATABLE READ)锁范围更小,尤其对非唯一条件查询;可通过 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; 临时调整
  • 显式加锁慎用 SELECT ... FOR UPDATE,优先考虑应用层幂等+乐观锁(如 UPDATE ... SET version=version+1 WHERE id=? AND version=?

超时参数能调但不能依赖

innodb_lock_wait_timeout 默认 50 秒,可在会话级临时调大:

SET SESSION innodb_lock_wait_timeout = 120;
但这是掩耳盗铃。真正该调的是 wait_timeoutinteractive_timeout(控制空闲连接自动断开时间),防止应用不关连接导致事务长期挂起。生产环境不建议全局调高 innodb_lock_wait_timeout,它掩盖的是设计缺陷,不是性能瓶颈。

并发异常处理的关键不在兜底重试逻辑多漂亮,而在于让锁不出现——或者出现后 1 秒内就释放。很多团队花半天写重试装饰器,却没查过慢查询日志里那条跑了 8 秒的 UPDATE 为什么没走索引。