问题现象:系统在空闲一段时间后,首次访问数据库时报错
Communications link failure,错误日志显示“Last packet received was 1,594,079 milliseconds ago(约26分钟)”。即使配置了连接池验证,问题依然反复出现。
本文将完整复盘该问题的排查思路、踩过的坑、最终解决方案,并详解 Druid 连接池关键参数配置,帮助大家避免重蹈覆辙。
一、问题初现:不只是“超时”那么简单
最初,我们看到典型的 MySQL 通信异常:
Caused by: com.mysql.cj.exceptions.CJCommunicationsException: Communications link failure
The last packet successfully received from the server was 1,594,079 milliseconds ago.
直觉告诉我们:数据库连接被断开了。
于是我们检查了 MySQL 的 wait_timeout 和 interactive_timeout,发现均为 7200 秒(2小时)——远大于 26 分钟。
结论:不是 MySQL 主动断连,而是中间网络设备(如云厂商 SLB、NAT 网关、防火墙)在空闲约 15–30 分钟后切断了 TCP 连接。
二、第一次尝试:配置连接池验证
我们立即为 Druid 连接池添加了基础防护:
test-on-borrow: true
validation-query: SELECT 1 FROM DUAL
min-evictable-idle-time-millis: 300000 # 5分钟
test-while-idle: true
预期效果:每次借出连接前先验证,确保连接有效。
结果:问题依旧!更诡异的是,日志中开始频繁出现:
ERROR druid.sql.Statement - execute error. SELECT 1 FROM DUAL
这说明:Druid 确实在验证连接,但验证本身失败了——连接已被网络中断。
三、深入排查:为什么验证失败还会报业务错误?
通过分析 Druid 源码和日志,我们发现:
- • 虽然
test-on-borrow=true能检测到坏连接, - • 但默认情况下,验证失败会直接抛出异常,不会自动重试!
这意味着:用户请求会直接失败,体验极差。
关键突破:启用 Druid 的 异常分类器(Exception Sorter) ,让连接池能识别“可恢复的网络异常”,并自动重试获取新连接。
四、最终解决方案:三重防护体系
1、启用 MySQL 异常分类器(核心!)
在 connection-properties 中显式指定:
connection-properties:
druid.exceptionSorter: com.alibaba.druid.pool.vendor.MySqlExceptionSorter
✅ 作用:当
SELECT 1验证失败时,Druid 会识别这是“网络断连”而非 SQL 错误,自动 discard 坏连接,并重试获取新连接,对业务透明。
2、合理设置空闲回收时间
根据网络设备超时(约 26 分钟),我们将空闲时间设为 10–15 分钟,留足安全余量:
min-evictable-idle-time-millis: 600000 # 10分钟
max-evictable-idle-time-millis: 900000 # 15分钟
time-between-eviction-runs-millis: 30000 # 每30秒检查一次
✅ 作用:在连接被网络中断前,主动回收,大幅降低验证失败概率。
3、关闭生产环境的 removeAbandoned(重要!)
初期我们启用了:
remove-abandoned: true
remove-abandoned-timeout-millis: 180000 # 3分钟
但 Druid 日志警告:
WARN - removeAbandoned is true, not use in production.
原因:它可能误杀真正的长事务(如耗时 5 分钟的报表导出),导致 Communications link failure。
✅ 生产环境必须关闭,改用良好编码规范(如 Spring 事务、try-with-resources)防止泄漏。
五、Druid 关键参数详解(生产推荐配置)
| 参数 | 推荐值 | 说明 |
|---|---|---|
test-on-borrow | true | 借出时验证,确保连接有效(牺牲微秒级性能,换稳定性) |
validation-query | SELECT 1 | 轻量验证 SQL,比 SELECT 1 FROM DUAL 更标准 |
min-evictable-idle-time-millis | 600000 (10分钟) | 必须 < 网络设备超时(如阿里云 SLB 默认 15 分钟) |
exceptionSorter | MySqlExceptionSorter | 自动识别网络异常,触发重试(解决验证失败仍报错的关键!) |
remove-abandoned | false | 生产环境禁用,避免误杀长事务 |
keep-alive | true | 启用 Druid 内部保活机制 |
filters | stat,wall,slf4j | 开启 SQL 监控、防火墙、统一日志 |
完整配置示例:
dynamic:
druid:
initial-size: 10
min-idle: 10
max-active: 40
max-wait: 30000
time-between-eviction-runs-millis: 30000
min-evictable-idle-time-millis: 600000
max-evictable-idle-time-millis: 900000
validation-query: SELECT 1
validation-query-timeout: 3
test-while-idle: true
test-on-borrow: true
test-on-return: false
# 核心:自动处理网络断连
connection-properties:
druid.exceptionSorter: com.alibaba.druid.pool.vendor.MySqlExceptionSorter
druid.stat.mergeSql: true
druid.stat.slowSqlMillis: 5000
filters: stat,wall,slf4j
keep-alive: true
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
# 生产环境关闭泄漏回收
remove-abandoned: false
log-abandoned: false
六、经验总结
- 1. 不要只看 MySQL 超时:云环境、NAT、防火墙才是“隐形杀手”;
- 2.
test-on-borrow=true是基础,但不够:必须配合exceptionSorter才能自动重试; - 3. 生产环境禁用
removeAbandoned:它治标不治本,还可能引发新问题; - 4. 连接池配置要“适配网络” :
minEvictableIdleTimeMillis必须小于中间设备超时; - 5. 长事务要拆分:避免在
@Transactional方法中调用外部耗时接口。
七、结语
数据库连接断连问题看似简单,实则涉及 网络、连接池、驱动、业务代码 多层协作。只有理解每一层的行为,才能构建真正高可用的系统。
希望本文能帮你少走弯路。如果觉得有用,欢迎转发给团队同学,一起提升系统稳定性!
技术无小事,细节定成败。