MySQL事务无执行时长限制,仅innodb_lock_wait_timeout控制锁等待超时(默认50秒),超时报错而非自动回滚;防阻塞需监控空闲/未提交长事务并应用层严格管控事务边界。

MySQL 事务超时靠的是 innodb_lock_wait_timeout,不是事务本身有“执行时长限制”
很多人以为设个“事务超时”就能让跑太久的 BEGIN...COMMIT 自动回滚,其实 MySQL 没这机制。真正起作用的是锁等待超时:当一个事务想更新某行,但那行正被另一个未提交事务锁住,它最多等 innodb_lock_wait_timeout 秒,超时就报错 Lock wait timeout exceeded,而不是让当前事务“运行超时”。
所以,防阻塞的关键不是拦住长事务,而是让它别卡在锁上等太久。
-
innodb_lock_wait_timeout默认是 50 秒,线上建议调低(比如 10~25 秒),避免请求长时间挂起 - 这个参数是会话级的,可动态改:
SET SESSION innodb_lock_wait_timeout = 15; - 全局修改要写进配置文件:
innodb_lock_wait_timeout = 15,然后重启或用SET GLOBAL(注意权限和持久性) - 它只影响行锁等待,不影响表锁、MDL 锁或事务空闲时间;长事务即使没加锁,照样可能拖慢 purge、阻塞 DDL
真正该监控和干预的是“空闲事务”和“未提交事务时长”
用户看到“阻塞”,往往是因为事务 BEGIN 了却迟迟不 COMMIT 或 ROLLBACK,比如应用异常退出、代码漏写提交、调试时忘了收尾。这类事务不争锁也占资源,还会拖慢 MVCC 清理。
- 查活跃长事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW() - trx_started) > 60; - 配合
PROCESSLIST看对应线程:SELECT * FROM information_schema.PROCESSLIST WHERE ID = ?; - DBA 脚本里常结合
trx_state = 'RUNNING'和trx_started时间戳做自动 kill(慎用,先确认业务影响) - 应用层更靠谱的做法:在 ORM 或数据库连接层加事务生命周期钩子,比如 Go 的
sql.Txdefer 提交/回滚,Java 的@Transactional(timeout = 30)(注意这是应用层控制,不等于 MySQL 生效)
客户端连接超时(wait_timeout / interactive_timeout)不能替代事务控制
这两个参数控制的是“连接空闲多久断开”,和事务是否提交无关。一个事务里执行了 SQL 后停住不动,只要连接没断、事务没结束,wait_timeout 就不会触发。
-
wait_timeout默认 28800 秒(8 小时),对长事务毫无约束力 - 它只在连接无任何命令交互时倒计时,一旦事务内执行了
UPDATE或SELECT,计时就重置 - 误设过小(如 30 秒)反而会导致正常长查询被断连,报错
MySQL server has gone away - 它和
innodb_lock_wait_timeout完全不同维度,别混用
应用端必须自己管住事务边界,MySQL 不会替你“兜底”
MySQL 从不主动终止一个正在运行的事务,哪怕它跑了两小时、锁了十万行。它只会在别人来抢锁时说“你等太久了”,或者连接断了才清理。
- 所有事务必须显式
COMMIT或ROLLBACK,不能依赖连接关闭自动回滚(除非用autocommit=0且连接异常中断,但行为不可靠) - Web 请求场景下,务必在 HTTP handler 结束前完成事务,别扔给异步 goroutine 或线程去提交
- 使用连接池时,注意有些池(如 HikariCP)支持
leakDetectionThreshold,能发现未关闭的事务,但仍是应用层补救 - 最易忽略的一点:存储过程里的事务,如果中间
EXIT HANDLER没写ROLLBACK,出错后事务仍挂着
事务超时这件事,MySQL 只管“等锁”这一小段,其余全靠应用自己掐表、设限、兜底。指望配置一个参数就一劳永逸,迟早掉坑里。










