在Java或各类后端语言访问MySQL时,连接池处在应用与数据库之间,承担连接的创建、复用与销毁。很多团队在压测中发现TPS上不去,监控却显示MySQL的CPU和IO都不高,其实问题出在应用进程里的连接池:要么连接数太少导致请求排队,要么连接数过多把MySQL的max_connections打满,引发拒绝服务。理解连接池在应用端的运作方式,并从配置层面做针对性优化,是低成本提升稳定性的手段。

连接数映射与maximumPoolSize设定
连接池最核心的参数是最大连接数,在HikariCP里叫maximumPoolSize,在Druid里叫maxActive。这个值不能直接拍脑袋决定。MySQL服务端有一个max_connections系统变量,限制了全局允许的同时连接上限。如果部署了多个应用节点,那么所有节点连接池最大值之和必须明显小于max_connections,留出足够余量给运维命令和副本同步。
另一个容易被忽略的约束来自应用自身线程模型。以常见的Web服务为例,每个HTTP请求通常由一个业务线程处理,若线程池大小为200,而连接池只有10,那么190个线程会在获取连接时阻塞。反过来,若连接池设为500但数据库只能承受300,多余请求会在MySQL侧失败。经验公式是:单节点maximumPoolSize约等于(数据库可分配连接数 / 节点数)乘以0.8,再结合慢查询比例微调。下面是一段Spring Boot中HikariCP的配置示例:
spring:
datasource:
hikari:
# 假设mysql max_connections=400,共4个应用节点
# 每节点预留余量后约为 400/4*0.8 = 80
maximum-pool-size: 80
minimum-idle: 10
# 连接最长存活时间,小于数据库或中间件超时
max-lifetime: 1800000
# 空闲连接回收时间
idle-timeout: 600000
# 获取连接最大等待毫秒
connection-timeout: 3000
当minimum-idle设置得过低,流量突增时连接池需要临时建立物理连接,这个过程涉及TCP握手和MySQL认证,延迟可能达到几十毫秒。设置合理的空闲下限,可以让常驻连接应对常规波动。但minimum-idle也不是越大越好,过多长期空闲连接会占用MySQL的会话资源,尤其在多租户实例上容易引起其他库的连接挤占。
连接回收机制与超时参数
应用端连接池必须主动回收那些已经“失联”或“过长闲置”的连接,否则会出现连接泄漏。所谓连接泄漏,是指代码里拿到连接后因为异常没走close,连接池以为还在使用中,久而久之可用连接耗尽。HikariCP提供了leakDetectionThreshold,当连接被借出超过该毫秒数未归还,会打印告警栈,帮助定位未关闭的地方。
maxLifetime则用于对付中间件或MySQL自身的超时。例如云数据库代理可能在900秒无活动后悄悄断开TCP,而应用连接池若不知道,借到这种“死连接”就会报Communications link failure。把maxLifetime设成小于基础设施超时的值(如上面示例的1800秒),连接池会在到期前主动淘汰并重建,业务无感知。对应的Druid参数叫maxEvictableIdleTimeMillis和timeBetweenEvictionRunsMillis,通过后台线程定期把过期连接踢出。
// Druid 连接池回收相关配置片段
DruidDataSource ds = new DruidDataSource();
ds.setMaxActive(80);
ds.setMinIdle(10);
// 连接最大存活毫秒
ds.setMaxEvictableIdleTimeMillis(1800000);
// 检测线程运行间隔
ds.setTimeBetweenEvictionRunsMillis(60000);
// 回收时是否校验有效性
ds.setTestWhileIdle(true);
ds.setValidationQuery("SELECT 1");
除了时间维度,还要关注connectionTimeout。它控制应用线程从池里等连接的最长时间。如果设得太大,前端请求会大量挂起;设得太小,在瞬时高峰会直接抛异常。一般Web接口整体超时在3秒左右,所以connectionTimeout设2000到3000毫秒较为合理,配合熔断降级,避免雪崩。此外,MySQL侧的wait_timeout和interactive_timeout也应与池的maxLifetime协同,防止两边策略冲突。
连接探活与validation策略
连接池借出连接前,要不要先发一句测试SQL确认数据库可达?这就是探活。HikariCP早期用connectionTestQuery,后来推荐用jdbc4的isValid方法,通过connectionTimeout之外的轻量机制检查。如果强行配置connectionTestQuery("SELECT 1"),每次借连接都多一次网络往返,高并发下非常浪费。而testOnBorrow在Druid里默认关闭,正是出于同样性能考虑。
更优的做法是testWhileIdle:只在连接空闲被回收线程检查时探活,借出时不做同步校验,配合maxLifetime提前淘汰,基本杜绝死连接被业务用到。下表对比了常见策略差异:
| 策略 | 检查时机 | 性能影响 | 适用场景 |
|---|---|---|---|
| testOnBorrow | 借出时 | 高,每次多SQL | 极不稳定网络 |
| testWhileIdle | 回收空闲时 | 低,后台执行 | 绝大多数生产 |
| testOnReturn | 归还时 | 中,归还慢 | 少使用 |
| maxLifetime淘汰 | 到期前 | 极低 | 有中间件超时 |
在MySQL 8.0驱动下,还可以开启usePipelineAuth与cachePrepStmts来减少连接建立与语句编译开销,让池化效果更明显。对于读写分离架构,应用端连接池往往配合 shardingsphere 或自研路由,此时每个物理库应有独立池配置,不能混用同一个dataSource,否则某库慢查询会拖垮全部流量。通过精细化的探活与隔离,应用端连接管理才能真正发挥MySQL服务端的能力。