mysql事务泄漏本质是连接占用后事务未提交/回滚,导致锁卡住、连接堆积、后续请求阻塞;最直接信号是show processlist中大量command为sleep、time持续超300秒且info为空,配合innodb_trx中trx_state为running且trx_started异常久,说明事务失控占锁不释放。

MySQL事务泄漏不是“事务自己漏了”,而是连接被占用后事务没提交/回滚,导致锁卡住、连接堆积、后续请求阻塞——它不报错,但系统会越来越慢。
SHOW PROCESSLIST里一堆Sleep连接且Time持续上涨
这是最直接的信号。执行 SHOW PROCESSLIST; 后,重点关注:
- Command 列为 Sleep
- Time 值远超业务正常耗时(比如 > 300 秒)
- Info 列为空或只有 NULL
这类连接大概率是应用拿了连接、开了事务,但没走完就“丢下不管”了(比如提前 return、异常未捕获、异步回调没执行)
INNODB_TRX显示大量RUNNING事务且TRX_STARTED时间异常久
运行 SELECT * FROM information_schema.INNODB_TRX\G,检查:
- TRX_STATE 是 RUNNING 或 LOCK WAIT
- TRX_STARTED 时间戳比当前时间早几小时甚至更久
- TRX_MYSQL_THREAD_ID 能和 SHOW PROCESSLIST 中的 ID 对上
说明事务实际还在跑,但应用层早已失去控制——它占着行锁、间隙锁,别的事务一碰就卡住
HikariCP的max-lifetime配置大于MySQL的wait_timeout
这不是事务泄漏的根源,但会让泄漏后果更严重:
- MySQL 的 wait_timeout 默认 28800 秒(8 小时),连接空闲这么久会被强制断开
- 如果 HikariCP 的 max-lifetime 设成 36000(10 小时),那连接池会把一个 MySQL 已经 kill 掉的连接再分给应用
- 下一次 executeQuery() 直接抛 CommunicationsException,而事务状态仍留在 MySQL 端,锁不释放
务必让 max-lifetime ≤ wait_timeout - 60,并配 keepalive-time=30000 主动探活
@Transactional方法里提前return导致SqlSession未解绑
Spring + MyBatis 场景下高频踩坑点:
- 在 @Transactional 方法里写了 if (xxx) return ResponseEntity.ok();
- 方法退出,但事务没提交,MyBatis 的 SqlSession 还绑在当前线程上
- 连接无法归还池子,等效于泄漏
必须:
- 把事务边界收窄,只包裹真正需要原子性的逻辑
- 避免在事务方法内做 HTTP 调用、文件读写等长耗时操作
- 确保所有分支路径都走到事务提交/回滚(包括异常分支)
事务泄漏最难缠的地方在于:它不抛异常,日志里也几乎没痕迹,只能靠 SHOW PROCESSLIST 和 INNODB_TRX 实时交叉验证;一旦发现,优先 KILL 掉长时间 RUNNING 的线程,再从应用代码里逐个排查 @Transactional 方法的退出路径和连接归还逻辑。











