事务超时被终止是因等待锁而非数据库慢或连接断,应优先查未提交的长事务;运行select * from information_schema.innodb_trx where trx_state = 'running' and trx_started。

这不是数据库慢了,也不是连接断了,而是你的事务在等锁,等满 innodb_lock_wait_timeout(默认 50 秒)后被 MySQL 主动终止;优先查谁没提交、谁霸着锁不放,而不是调大超时时间。
查未提交的长事务:盯 INNODB_TRX 里“空挂”的线程
报错语句本身往往执行飞快,真正卡住的是前面某个开着却不动的事务。关键不是看慢日志,而是直接查实时事务状态:
- 运行
SELECT * FROM information_schema.INNODB_TRX WHERE trx_state = 'RUNNING' AND trx_started ,重点关注 <code>trx_started超过 1 分钟的记录 - 检查
trx_query是否为空——空值说明事务开了但没发任何 SQL,纯属“占坑” - 比对
trx_mysql_thread_id和SHOW PROCESSLIST中状态为Sleep且Time很大的线程,基本就是它 - Spring 应用尤其常见:
@Transactional方法里调了 HTTP 或 RPC 却没设超时,或catch异常后忘了rollback
确认阻塞关系:别杀报错线程,要杀 blocking_pid
只看 INNODB_TRX 只知道“谁没提交”,不知道“谁被它堵住了”。必须联查锁等待链:
- MySQL 5.7 及之前:
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;,字段更直白,blocking_pid直接对应线程 ID -
blocking_query为空 ≠ 安全——可能刚执行完UPDATE就卡在应用层逻辑里了 - 真正该
KILL的是blocking_pid对应的线程,不是报错那个;杀错人会导致下一个请求立刻重蹈覆辙
别盲目调 innodb_lock_wait_timeout:它只是掩盖问题
把超时从 50 秒改成 300 秒,不会让锁释放得更快,只会让失败来得更晚、更难定位:
- 全局改(
SET GLOBAL innodb_lock_wait_timeout = 300)只影响新连接,当前连接不受影响 - 会话级临时调低(
SET SESSION innodb_lock_wait_timeout = 5)反而是好办法——能快速复现问题,逼出隐藏长事务 - 如果调大后错误变少,说明你掩盖了应用层事务控制缺陷,不是优化
- 误杀
User = 'system user'(主从同步线程)或定时任务线程,可能引发主从延迟甚至数据不一致
批量操作和索引缺失:这两类场景最容易放大锁冲突
批量更新/删除若没走索引,InnoDB 会升级为表锁或锁住大片间隙,让并发写几乎无法进行:
- 用
EXPLAIN确认你的WHERE条件是否命中索引;没索引的UPDATE ... WHERE status = 1可能锁住整张表 - 批量操作别在一个事务里循环执行 N 条
UPDATE,拆成小批次(如每 100 行一个事务),缩短单事务持锁时间 - 高并发写同一张表时,考虑把默认隔离级别
REPEATABLE READ改为READ COMMITTED(需连接池初始化时设置sessionVariables=transaction_isolation=READ-COMMITTED),避免间隙锁扩大锁定范围 - 注意副作用:
READ COMMITTED下可能出现不可重复读,业务若依赖“同一事务内多次SELECT结果一致”,就得重审逻辑
最常被忽略的一点:锁等待超时错误发生后,持有锁的事务依然活着,且很可能还在继续执行——它不因别人超时而自动中断。所以定位必须快,KILL 必须准,否则问题会像滚雪球一样越积越多。











