一、先看一个真实故障场景
凌晨大促刚开始,线上监控突然刷屏,接口大面积报同一个异常:
SQLTransientConnectionException: HikariPool-1 - Connection is not available,
request timed out after 30000ms
这个报错的含义很直白:连接池里的连接全部被占用,新请求排队等待 30 秒仍然拿不到连接。很多团队的第一反应是「池子太小,把 maximumPoolSize 调大」,结果往往越调越糟——连接从 20 加到 200,数据库连接数打满,CPU 飙升,问题反而更严重。今天就从参数、公式、排查三板斧三个层面,把连接池这件事讲透。
二、HikariCP 核心参数逐项拆解
1. maximumPoolSize 与 minimumIdle
- maximumPoolSize:池中允许存在的最大连接数(含空闲与在用)。
- minimumIdle:池中保持的最小空闲连接数。注意 HikariCP 默认 minimumIdle = maximumPoolSize,即默认全量常驻、不收缩。
池大小不是拍脑袋定的,经典起步公式是:
maximumPoolSize = ((cpu核数 * 2) + 磁盘数)
4 核单机大约 9~10 个连接。但请记住:这只是起点,不是答案,最终要靠压测校准(第七节讲方法)。
2. 四个时间参数的关系
- connectionTimeout:客户端等待获取连接的超时,默认 30000ms,生产建议收紧到 3000~5000ms,避免请求长时间挂起。
- idleTimeout:空闲连接被回收的时间,默认 600000ms(10 分钟)。
- maxLifetime:连接最大存活时间,默认 1800000ms(30 分钟)。必须小于数据库侧的空闲断开时间,否则应用层拿到的可能是已被服务端关闭的「僵尸连接」。
- keepaliveTime:HikariCP 2.6+ 新增,连接空闲时主动发探测包保活,防止中间设备静默掐断连接;它必须小于 idleTimeout。
这几个参数的硬约束可以记成:maxLifetime < 数据库侧空闲断开时间、keepaliveTime < idleTimeout、validationTimeout < connectionTimeout。三者只要有一个配反,就会出现「连接莫名失效」的偶发异常。
3. validationTimeout 与 initializationFailTimeout
- validationTimeout:校验连接是否有效的最长等待,默认 5000ms,应明显小于 connectionTimeout。
- initializationFailTimeout:启动时连不上数据库是否快速失败。生产环境建议保持默认(连不上就启动失败),别让应用带病启动,否则会出现「服务起来了、流量一进来全 500」的诡异状态。
三、连接池到底该开多大:先理解池的本质
连接池不是「并发上限」,而是「吞吐缓冲」。一条粗略的估算公式:
池每秒可处理的请求数 ≈ 连接数 / 单请求平均持连接时长
举例:单连接处理一笔事务需要 20ms,10 个连接理论上每秒能处理 500 笔;如果业务高峰 TPS 需要 800,10 个不够,20 个基本够。反过来,如果每个请求平均持连接 200ms(典型的慢 SQL 拖累),10 个连接每秒只能处理 50 个请求——这时候拼命加连接池只是在掩盖慢查询,池子越大,数据库端积压的会话越多,内存占用越高(PostgreSQL 每个连接约占用 5~10MB 内存)。
正确顺序永远是:先优化 SQL,再谈调池。
四、一份可落地的生产配置
spring:
datasource:
hikari:
pool-name: app-db-pool
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 5000
idle-timeout: 600000
max-lifetime: 1200000 # 小于数据库侧空闲断开时间
keepalive-time: 300000
validation-timeout: 3000
leak-detection-threshold: 10000
# PostgreSQL 可省略 connection-test-query(HikariCP 原生用 isClosed 探测)
# 若使用 MySQL,建议显式配置: connection-test-query: SELECT 1
两个容易忽略的点:一是 leak-detection-threshold 一定要开,这是后面排查泄漏的钥匙;二是 PG 与 MySQL 的探活机制不同,MySQL 场景下显式配置 SELECT 1 更稳妥。
五、连接耗尽与泄漏排查三板斧
第一板斧:看监控定位是「不够用」还是「被泄漏」
观察池的三个指标:active(在用)连接数、idle(空闲)连接数、pending(等待获取)线程数。如果 active 长期打满、pending 持续增长,同时数据库侧 CPU 与慢查询都不高,基本可以判定是连接泄漏——连接被业务代码借走后再也没还回来。
第二板斧:打开泄漏检测
当 leak-detection-threshold 设置后(如 10s),日志会出现:
Apparent connection leak detected
这条日志会带出持有连接超过阈值的线程栈,直接定位到业务代码位置。
第三板斧:最常见的四类泄漏元凶
- 事务内做远程调用、发 MQ、调外部 API——长事务长期占着连接;
- 手动开启事务后忘记提交/回滚,异常被 try-catch 吞掉后连接带着未提交事务回到池里;
- 线程池任务里拿到连接后异常退出没有释放;
- @Transactional 传播行为理解错误,内层方法意外参与外层事务,导致连接持有时间被拉长。
手管连接的正确姿势是 try-with-resources:
try (Connection conn = dataSource.getConnection()) {
conn.setAutoCommit(false);
// 业务操作...
conn.commit();
} catch (Exception e) {
// 异常时连接会自动关闭回滚,日志记录后抛出由上层处理
log.error("execute failed", e);
throw e;
}
更推荐的做法是业务层直接用 JdbcTemplate / MyBatis / Spring 的 DataSourceUtils,把连接的获取与释放交给框架,从源头减少手管连接的机会。
六、MySQL → PostgreSQL 迁移后连接池的三个隐藏坑
很多团队迁移完 PG 只改了 URL 和驱动,连接池参数沿用 MySQL 时代的配置,结果踩了三个隐蔽的坑:
坑一:空闲会话的断开策略完全不同
MySQL 的 wait_timeout 默认 8 小时,超时后服务端主动断开;PostgreSQL 默认不会主动断开空闲连接(除非配置 idle_in_transaction_session_timeout 或接入层策略)。如果 DBA 在 PG 侧配了会话断开策略,而应用侧 maxLifetime 还保持着 30 分钟,就可能出现「连接已被服务端关闭、应用仍以为可用」的偶发报错。
坑二:慢 SQL 会无限占用连接
PG 没有 MySQL 那种隐式的超时保护,一条失控的 SQL 可以把连接挂住很久。建议在连接串或初始化 SQL 里设置 statement 级超时:
spring:
datasource:
url: jdbc:postgresql://db.example.com:5432/app_db?tcpKeepAlive=true&socketTimeout=30
hikari:
connection-init-sql: SET statement_timeout = 15000
坑三:云环境中间设备的静默断连
云厂商的负载均衡或网络设备可能静默回收空闲连接,连接池对此毫无感知。组合拳是:JDBC URL 开启 tcpKeepAlive=true + HikariCP 的 keepaliveTime 主动探测。
七、用压测验证池大小是否合理
用 wrk 或 JMeter 逐步加压,同时盯三条曲线:
- TPS 是否随并发上升而上升——若不再增长,说明瓶颈已到;
- P99 延迟是否稳定——抖动变大往往是排队开始;
- 池 active 是否触顶——触顶且 TPS 不再涨,先查慢 SQL(数据库 slow log / pg_stat_activity),再考虑加连接。
压测结论比任何公式都可信。改完参数后还要观察 24 小时基线,确认峰值时段不再出现 pending 堆积。
八、最佳实践清单
- 池大小从公式起步、压测校准,不要拍脑袋也不要照抄网上配置;
- 参数硬约束:maxLifetime < 数据库空闲超时;keepaliveTime < idleTimeout;validationTimeout < connectionTimeout;
- 生产环境开启 leak-detection-threshold 并配置告警;
- 事务内严禁远程调用与网络 IO;手管连接一律 try-with-resources;
- 连接池指标接入监控(HikariCP 原生支持 Micrometer,可采集 hikaricp.connections.active / idle / pending);
- 保持 JDBC 驱动及时升级——旧版 PG 驱动存在连接状态误判的已知问题。
连接池是数据库访问的第一道闸门,参数调优的本质是理解「连接的生命周期」与「业务的持有时长」。把生命周期理顺、把泄漏堵死,比盲目调大 pool-size 有效一百倍。