事务未提交导致连接被锁死而非连接数上涨,本质是事务开启后未完成,连接无法归还池中;典型表现为show processlist中大量query/sleep状态且info含begin或为空,结合innodb_trx查trx_state=running且trx_query为空可确认。

事务没提交,连接就卡住了
事务泄漏不是连接没关,而是事务开着、连接被占着、池子里的连接数没涨但实际已被锁死。最典型现象是:SHOW PROCESSLIST 里看到大量 Command 为 Query 或 Sleep、State 是 Waiting for table metadata lock 或 starting、Info 字段显示未完成的 SQL(比如 BEGIN 后再无 COMMIT 或 ROLLBACK)。这类连接不会出现在 HikariCP 的 active 计数里,但 MySQL 线程一直挂着——因为事务没结束,连接不能归还池子。
Spring @Transactional 里手动 getConnection() 是高危操作
Spring 的声明式事务靠代理拦截方法入口/出口来开启和提交事务,底层连接由 DataSourceTransactionManager 统一管理。一旦你在 @Transactional 方法里调用 dataSource.getConnection(),就绕过了 Spring 的事务上下文,拿到的是一个“裸连接”,后续不管有没有 close(),这个连接都不会自动参与事务提交或回滚。
- 它不会触发 Spring 的事务同步器,
commit()和rollback()都不会被调用 - 如果方法执行完没手动
commit(),连接会一直留在事务中,MySQL 侧状态就是ACTIVE或Locked - HikariCP 的
leakDetectionThreshold不会报警,因为它只监控连接借出/归还,不感知事务生命周期
怎么快速定位事务泄漏点
别只盯着代码有没有 close(),先看数据库现场:
- 执行
SELECT * FROM information_schema.processlist WHERE user = 'your_app_user' AND (state LIKE '%lock%' OR info LIKE '%BEGIN%' OR info IS NULL) ORDER BY time DESC;—— 找出长时间挂起、且没有明确 SQL 执行内容的连接 - 对可疑连接 ID 执行
SELECT trx_id, trx_state, trx_started, trx_query FROM information_schema.innodb_trx WHERE trx_mysql_thread_id = ?;,确认是否处于RUNNING或LOCK WAIT状态且trx_query为空 - 结合应用日志,搜索关键词
TransactionCallbackWithoutResult、doInTransaction、getConnection,重点检查自定义事务模板或 JDBC 模板回调里的连接使用逻辑
异步 + 事务 = 隐形泄漏温床
在 @Transactional 方法里启动 CompletableFuture 或线程池任务,并在里面操作数据库,事务上下文不会自动传播过去。结果就是主线程事务提交了,但异步线程里的连接还在跑、事务没关、连接没归还。
-
@Async方法默认不继承事务,即使加了@Transactional,也是新事务,和父事务无关 - 用
TransactionSynchronizationManager手动传递connection或dataSource极易出错,不推荐 - 真正安全的做法:把需要异步执行的 DB 操作拆成独立的
@Transactional方法,由异步线程单独调用
事务泄漏最难缠的地方在于:它不报错、不触发连接池泄漏日志、连接数看着正常,但数据库锁资源持续堆积、慢查询增多、后续请求莫名卡住。排查时一定要跨层看——既要看 MySQL 的 innodb_trx,也要看应用线程栈里有没有“开了事务却没走到终点”的调用链。











