最后更新:2026-10-28
适用场景:Spring Boot 3.x、JDBC / MyBatis、HikariCP

接口偶尔卡住几十秒,随后日志里出现 Connection is not available, request timed out。第一反应往往是把连接池从 10 调到 100。先别急:连接不够用,可能是并发确实增加了,也可能是慢 SQL、长事务或没有及时归还连接。只改池大小,常常只是把报错推迟。

先看异常发生在哪个池

保留完整异常和发生时间,确认是哪一个 DataSource、哪个实例报错。多数据源项目里,订单库和报表库可能各有一个池;一个池满了,不代表所有数据库都满了。再核对运行中的配置,而不是只看本地的 application.yml。Spring Boot 在 JDBC Starter 可用时通常优先选 HikariCP,自定义数据源时配置路径也可能不同。Spring Boot 数据库文档说明了选择与配置方式。

HikariCP 的 maximumPoolSize 是空闲加使用中的连接总上限。到达上限且没有空闲连接时,借连接的线程会等待,超过 connectionTimeout 才报错。这个超时表示等连接超时,不等于 SQL 本身执行了那么久。HikariCP 配置说明给出了这两个参数的含义。

用指标区分“借不到”和“数据库不可用”

若项目已有受控的 Actuator 指标,可以在故障时看 jdbc.connections.active、jdbc.connections.idle、jdbc.connections.max,以及 Hikari 的 hikaricp 系列指标。只在可信网络和现有权限下查看,不要为了排查把 Actuator 全量暴露到公网。Spring Boot 指标文档列出了这些数据源指标。

active 长时间贴着 max、idle 为 0,说明连接一直被占用;短暂尖峰后恢复,则更像并发峰值。再结合数据库侧的活动会话和慢查询记录,看它们在执行 SQL、等待锁,还是停在应用代码里。连接池指标只告诉你“占了多少”,不能单独证明是连接泄漏。

沿着连接持有时间查

先找慢 SQL、缺失索引、锁等待,再看事务边界。比如一个 @Transactional 方法先更新数据库,然后同步调用外部 HTTP 接口,外部接口卡住时,事务可能一直持有连接。把远程调用移出事务,不是机械地换一下代码顺序:要先想清失败补偿与一致性要求。相关事务边界可对照Spring Boot @Transactional 不回滚怎么排查。

手写 JDBC 时尤其要确认连接、语句和结果集会被关闭。try-with-resources 可以让正常返回和抛异常都走到清理逻辑:

try (Connection connection = dataSource.getConnection();
     PreparedStatement statement = connection.prepareStatement(
             "select id from demo_order where id = ?")) {
    statement.setLong(1, orderId);
    try (ResultSet rows = statement.executeQuery()) {
        return rows.next();
    }
}

若是框架管理的连接,不要在业务代码里擅自关闭框架交给事务管理器的资源;先定位到底由谁获取、谁归还。HikariCP 可临时配置 leakDetectionThreshold,让借出过久的连接打印调用栈,但长事务也可能触发告警,它只是线索,不是泄漏判决。排查完及时撤掉临时阈值,避免大量日志。

最后才评估池大小

确认 SQL、事务和资源释放没有异常后,再结合实例数、数据库最大连接数、实际并发与数据库处理能力调整。例如 8 个实例都设 maximumPoolSize: 100,理论上可能占到 800 个连接;数据库未必承受得住。别把连接池上限当作吞吐量旋钮。

复测时同时记录接口延迟、连接池等待数、数据库 CPU 和慢查询。若报错消失却数据库延迟明显上升,问题并没有解决。连接本身建不起来时,应先看Spring Boot 连接 MySQL和DataSource 启动失败排查,那与“池已耗尽”不是同一类故障。

参考资料

Spring Boot 3.5:SQL Databases
Spring Boot 3.5:DataSource Metrics
HikariCP:Configuration

更新记录

2026-10-28:创建文章,补充连接池指标、连接持有时间与调参边界。