Lock wait timeout exceeded 是锁等待超时,非死锁;错误表明事务等待行/页锁超时(默认50秒)被终止,但持锁事务仍存活,需通过 INNODB_TRX 与 INNODB_LOCK_WAITS 定位阻塞源并 KILL blocking_thread。

这不是死锁,是锁等待超时;死锁会报“Deadlock found”,而 Lock wait timeout exceeded 说明事务在等锁,但等太久被 MySQL 主动放弃了。
怎么确认不是死锁而是锁等待超时
看到错误信息里明确写着 Lock wait timeout exceeded; try restarting transaction,就不是死锁。死锁的报错固定是 Deadlock found when trying to get lock,且 InnoDB 会自动回滚一个事务。而这个错误意味着:当前事务卡在等锁,等满 innodb_lock_wait_timeout(默认 50 秒)后被强制终止,但**持有锁的那个事务还活着、没提交、也没报错**。
- 查日志或监控,如果只看到这一种报错、没伴随
Deadlock字样,基本排除死锁 - 执行
SHOW ENGINE INNODB STATUS\G,翻到LATEST DETECTED DEADLOCK部分——如果为空或时间对不上,就不是刚发生的死锁 - 应用层重试后仍频繁报这个错,大概率是某个长事务或未提交事务在持续霸占锁
用 INNODB_TRX 和 INNODB_LOCK_WAITS 快速定位阻塞源
别查慢 SQL 或索引,先抓“挂着不走”的事务。核心是两步:找长时间 RUNNING 的事务 → 找谁在等它。
- 运行
SELECT * FROM information_schema.INNODB_TRX WHERE trx_state = 'RUNNING' AND trx_started ,重点关注 <code>trx_started超过 1 分钟、且trx_query为空(空挂事务)或执行着可疑操作(比如SLEEP()、HTTP 调用、大循环)的记录 - 拿这些
trx_mysql_thread_id去比对SHOW PROCESSLIST,看对应线程状态是不是Sleep且Time很大——八成就是它 - 再联查锁等待:
SELECT r.trx_id waiting_trx_id, r.trx_query waiting_query, b.trx_id blocking_trx_id, b.trx_query blocking_query FROM INFORMATION_SCHEMA.INNODB_TRX r INNER JOIN INFORMATION_SCHEMA.INNODB_LOCK_WAITS w ON r.trx_id = w.requesting_trx_id INNER JOIN INFORMATION_SCHEMA.INNODB_TRX b ON b.trx_id = w.blocking_trx_id - MySQL 8.0+ 可直接用
SELECT * FROM sys.innodb_lock_waits,字段更直观(waiting_pid/blocking_pid)
为什么不能直接 KILL 报错的那个线程
KILL 错误线程只是让当前请求失败,阻塞源还在,下个请求照常超时。真正该处理的是 blocking_trx_id 对应的那个事务。
- 盲目 KILL 容易干掉主库同步线程(
User是system user)或定时任务,引发数据不一致 - 先看
blocking_query是什么:如果是调试残留的BEGIN; UPDATE ...;没COMMIT,直接 KILL;如果是业务代码里@Transactional方法调了 RPC 却没设超时,就得改代码,不是杀就能解决的 - KILL 后务必验证:再跑一次锁等待查询,确认
blocking_trx_id消失,且新事务能正常获取锁
容易被忽略的三个深层原因
表面是事务没提交,根子往往在应用逻辑或表结构上。
- Spring 中
@Transactional方法内捕获异常但没手动rollback,或用了try-catch吞掉异常导致事务没结束 -
WHERE条件字段(如buyer_user_id)没建索引,UPDATE 触发全表扫描 → 行锁升级为间隙锁或表级锁 → 锁范围爆炸式扩大,极易阻塞其他事务 - 事务嵌套:外层事务开启后,子方法又开了新事务并操作同一行,形成“外层等子层提交,子层等外层释放锁”的隐式循环,日志里可能只看到一条 UPDATE 卡住,实际是两层事务互相锁死


















