核心矛盾是MySQL主动断连后连接池仍分配“僵尸连接”;需确保连接池探活配置(如validationQuery+testWhileIdle)生效且扫描间隔小于wait_timeout,禁用已弃用的autoReconnect。

直接改 wait_timeout 不是万能解法,反而容易掩盖连接泄露或探活配置缺失的真实问题。核心矛盾在于:MySQL 主动断连后,连接池仍把“已关闭”的连接当有效连接返回给业务代码。
为什么 wait_timeout=28800 会触发报错
MySQL 默认非交互式连接空闲 28800 秒(8 小时)后强制关闭连接;而 Java 连接池(如 HikariCP、Druid、DBCP)若未开启有效性校验,就会继续把这条“僵尸连接”分配出去。业务线程一执行 connection.createStatement() 或 executeQuery(),立刻抛出类似:
com.mysql.cj.jdbc.exceptions.CommunicationsException: The last packet successfully received from the server was ... ago. is longer than the server configured value of 'wait_timeout'.
这不是网络故障,是连接状态不同步——MySQL 认为它早关了,Java 还以为它活着。
testWhileIdle + validationQuery 必须配对生效
仅设 testWhileIdle=true 不起作用,必须同时指定一个轻量级 SQL 做心跳验证。不同连接池写法略有差异,但逻辑一致:
- HikariCP(推荐):
connection-test-query=SELECT 1(MySQL 8.0.19+ 用SELECT 1,旧版可用SELECT 1或/* ping */ SELECT 1) - Druid:
validation-query=SELECT 1+test-while-idle=true - DBCP2:
validationQuery=SELECT 1+testWhileIdle=true
关键点:validationQuery 必须是无副作用、毫秒级返回的语句;不能用 SELECT NOW() 或带函数的复杂查询,否则会拖慢空闲连接清理周期。
time-between-eviction-runs-millis 要小于 wait_timeout
连接池靠后台线程定期扫描空闲连接并验证,这个扫描间隔必须比 MySQL 的断连阈值更短,否则来不及清理就断了。例如:
- MySQL
wait_timeout=7200(2 小时),则连接池应设time-between-eviction-runs-millis=60000(1 分钟) - 若设成
300000(5 分钟),虽能降低开销,但极端情况下仍可能漏掉刚超时的连接 - HikariCP 中对应的是
connection-timeout和idle-timeout,注意idle-timeout必须 小于wait_timeout,否则池内连接永远等不到被驱逐
很多线上事故就卡在这里:MySQL 改成了 24 小时,连接池却还按默认 10 分钟扫描,结果空闲连接在池里“活”过 24 小时,一用就崩。
别依赖 autoReconnect=true
MySQL Connector/J 的 autoReconnect=true 参数早已被官方标记为 deprecated,且在事务中完全不可靠——它只在连接刚断时尝试重连,但不会回滚当前事务或恢复连接上下文。一旦发生 SQLException,业务逻辑大概率已处于不一致状态。
更危险的是,它会让问题延迟暴露:连接断了你不报错,而是静默重连,结果你根本发现不了连接池配置是否真生效、validationQuery 是否写错、网络是否真稳定。
真正要盯住的,是连接池监控页里的 activeCount、idleCount、poolingCount,以及 MySQL 端 SHOW PROCESSLIST 中 State='Sleep' 且 Time > 60 的连接数。这两边数字长期不匹配,说明探活机制没跑起来。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











