真泄漏需满足:连接数持续上涨、processlist中大量sleep且time超时、user固定为应用账号;应先通过show processlist和threads_connected确认现象,再结合wait_timeout调短、leak-detection-threshold设60000ms等配置主动暴露问题。

查 MySQL 当前 Sleep 连接和事务状态
连接池泄露常表现为大量连接卡在 Sleep 状态,同时事务未提交,锁持续占用。别等应用报错,直接连上数据库执行两行命令:
-
SHOW PROCESSLIST;—— 找出Command列为Sleep、Time值远超业务正常耗时(如 > 300)、且Info为空的连接;这些极大概率是被借出后没归还的“幽灵连接” -
SELECT * FROM information_schema.INNODB_TRX\G—— 检查TRX_STATE是否存在大量RUNNING或LOCK WAIT,重点看TRX_STARTED时间是否异常久远(比如几小时甚至一天前)
这两个结果交叉比对:如果某条 Sleep 连接的 ID 能在 INNODB_TRX 中找到对应 TRX_MYSQL_THREAD_ID,就坐实了“连接没还 + 事务卡住”双重问题。
HikariCP / Druid 泄漏检测必须开,且阈值要合理
连接池本身不报错 ≠ 没问题。HikariCP 默认关闭泄漏检测,Druid 的 removeAbandonedOnBorrow 也默认关着——不开等于蒙眼开车。
- HikariCP 配置项:
leakDetectionThreshold=60000(单位毫秒),表示连接被借出 60 秒未归还就触发告警,并打印完整调用栈;设太小(如 5000)易误报,太大(如 300000)失去意义 - Druid 配置项:
removeAbandonedOnBorrow=true+removeAbandonedTimeoutMillis=60000,配合logAbandoned=true,才能在日志里看到类似abandon connection及其堆栈 - ⚠️ 注意:Druid 的
removeAbandonedOnBorrow在高并发下可能误回收活跃连接,生产环境建议只开logAbandoned,靠日志人工筛查
Spring + MyBatis 下,@Transactional 提前 return 是高频雷区
这不是配置问题,是代码逻辑绕过了 Spring 事务管理器的清理流程。典型场景:Controller 层调用一个 @Transactional Service 方法,方法内部做了查询,然后直接 return ResponseEntity.of(...),事务还没提交,线程就退出了。
- 后果:MyBatis 的
SqlSession仍绑定在线程上,连接无法归还,事务长期挂起,锁不释放 - 检查点:所有带
@Transactional的方法,确认没有在事务块内return、未捕获的RuntimeException、或阻塞式外部调用(如 HTTP)后中断执行 - 修复方式:缩小事务边界(只包真正需要事务的操作),或确保方法体完整执行到底,哪怕只是加个空
finally块做兜底
max-lifetime 必须小于 MySQL 的 wait_timeout
这个参数错配会导致“连接已断,池子不知情”,进而引发更隐蔽的事务卡死。MySQL 不会主动通知连接池“我已断开”,它只等客户端发心跳或下次请求时才抛异常。
- 查 MySQL 实际值:
SHOW VARIABLES LIKE 'wait_timeout';(默认 28800 秒,即 8 小时) - HikariCP 必须设:
max-lifetime≤wait_timeout - 60,例如 MySQL 设为 3600(1 小时),HikariCP 就配 3540(59 分钟) - 同时确保:
idle-timeout≤max-lifetime,否则空闲连接比活跃连接活得还久,逻辑矛盾 - 验证效果:改完重启后,观察
SHOW PROCESSLIST中Sleep连接的Time值是否开始规律回落(接近max-lifetime设置值)
最麻烦的不是查不到线索,而是线索藏在 MySQL 的 Sleep 连接里,而你只盯着应用日志看——连接池泄露引发的长事务锁,本质是应用层资源生命周期管理失控,MySQL 只是被动承受者。











