SpringBoot3 + JDK17 实战复盘:HikariCP 连接耗尽,虚拟线程场景额外注意事项

生产高峰期偶现报错: could not get JDBC Connection; nested exception is com.zaxxer.hikari.pool.HikariPool$PoolTimeoutException: HikariPool‑1‑ Connection is not available

连接池耗尽,大量接口阻塞超时,重启服务临时恢复,一段时间之后再次复现。

SpringBoot3 默认依旧使用 HikariCP,但在 JDK17 模块化、虚拟线程、新的异常栈、资源关闭行为上,和 JDK8 存在细微差异;同时很多人升级之后直接沿用老 JDK8 的连接池配置,会埋下隐患。

不少同学遇到报错第一反应:直接调大maximum‑pool‑size。 盲目放大连接数只是治标不治本。真正根源集中在:连接泄露、长事务霸占连接、慢 SQL、线程模型与连接池不匹配,虚拟线程使用不当带来的新问题

本文基于 SpringBoot3 + JDK17,结合线上真实故障,梳理高频场景、错误 Demo、修复方案、生产编码检查清单。

前置:SpringBoot3.x 强制依赖 HikariCP;JDK17 强化AutoCloseable资源关闭,同时引入虚拟线程,给数据库连接管理带来新的注意点。

一、场景 1:手动获取 Connection,异常分支未关闭,发生连接泄露

即使在 JDK17,手动获取连接,如果异常、提前 return 绕过关闭逻辑,依旧会发生连接泄露。 JDK17 不会自动帮你回收数据库连接资源。

❌错误 Demo(升级项目经常遗留老 JDK8 写法)

ini 复制代码
@Service
public class StudentDbService {

    @Autowired
    private DataSource dataSource;

    public void queryStudent() throws SQLException {
        Connection connection = dataSource.getConnection();
        PreparedStatement ps = connection.prepareStatement("select * from t_student");
        ResultSet rs = ps.executeQuery();

        while (rs.next()) {
            //业务处理
        }
        //异常抛出,直接跳出方法,后面close完全不会执行,连接泄露
        int i = 1 / 0;

        rs.close();
        ps.close();
        connection.close();
    }
}

现象:每调用一次消耗一条数据库连接,连接池被慢慢吃满,最终报拿不到连接。

✅JDK7 + 推荐:try‑with‑resources,JDK17 下对 AutoCloseable 支持更完善,自动释放资源

ini 复制代码
public void queryStudent() throws SQLException {
    try (Connection connection = dataSource.getConnection();
         PreparedStatement ps = connection.prepareStatement("select * from t_student");
         ResultSet rs = ps.executeQuery()) {

        while (rs.next()) {
            //业务处理
        }
        int i = 1 / 0;
    }
}

生产准则:业务代码尽量不要手动 getConnection ,交给 MyBatis‑SpringBoot3 适配包管理连接生命周期。 如果迫不得已手动操作连接,先写好获取与回收闭环,再写业务逻辑 ;优先回调模板模式,业务逻辑写进回调,避免return、异常、if‑else 分支绕过资源回收。

二、场景 2:长事务霸占连接,SpringBoot3 环境依旧高频踩坑

@Transactional方法内部嵌入 RPC、HTTP 调用、重量级 IO,事务未提交,数据库连接一直被占用。

❌错误 Demo

java 复制代码
@Service
public class StudentService {

    @Autowired
    private StudentMapper studentMapper;
    @Autowired
    private RemoteFeign remoteFeign;

    @Transactional(rollbackFor = Exception.class)
    public void updateStudent() {
        studentMapper.updateInfo(new Student());
        // Feign远程调用,网络抖动阻塞,连接长期占用不释放
        remoteFeign.callThirdApi();
    }
}

SpringBoot3 的@Transactional底层逻辑没有改变:进入事务方法就获取连接,方法执行完毕才归还。

✅修复:远程调用移出事务;耗时非数据库操作可考虑队列异步处理

csharp 复制代码
public void updateStudent() {
    //远程调用放到事务外部
    remoteFeign.callThirdApi();
    doDbUpdate();
}

@Transactional(rollbackFor = Exception.class)
public void doDbUpdate() {
    studentMapper.updateInfo(new Student());
}

重要提醒:不是所有长事务都直接丢队列。如果多步数据库操作必须原子性,保留短事务,只把 RPC、IO、计算移出事务。队列解决的是耗时外部调用,不能替代数据库事务原子性。

三、场景 3:慢 SQL 长时间占用连接,JDK17 不解决 SQL 本身性能问题

升级 JDK 版本不会优化业务 SQL。缺失索引、复杂大查询,单条 SQL 执行几十秒,持续霸占连接。

排查手段:

  1. MySQL 执行show processlist观察长时间 Running 会话;
  2. SpringBoot3 Actuator 暴露 Hikari 连接池指标,监控活跃连接、连接持有时长。

application‑yaml 片段

yaml 复制代码
spring:
  datasource:
    hikari:
      connection-timeout: 30000
management:
  endpoints:
    web:
      exposure:
        include: metrics

✅处理:SQL 加索引优化;设置合理超时,禁止 SQL 无限阻塞。

四、场景 4:SpringBoot3 + JDK17 虚拟线程带来的新坑

JDK21 虚拟线程,SpringBoot3.2 + 支持虚拟线程。很多同学直接全部接口切虚拟线程,这里有连接池关键认知。

虚拟线程本身不消耗数据库连接池,虚拟线程多,并不代表可以无限放大 HikariCP 最大连接数。 大量虚拟线程同时访问数据库,依旧会排队等待有限的数据库连接。

误区:开启虚拟线程,直接把maximum‑pool‑size拉很大。

MySQL 服务端连接数上限有限,连接数过高会造成数据库上下文切换加剧、锁竞争,整体性能下降。

✅连接池大小参考原则 网上流传公式:maximum‑pool‑size = CPU核心数 *2 + IO数量

⚠️这只是初始参考值,不是固定标准答案

  • CPU 密集、SQL 执行很快,可以贴近公式;
  • IO 等待多、慢查询多,适度调大;
  • 多实例部署,需要除以实例数量,不能超过 MySQL 全局最大连接上限。

SpringBoot3 + JDK17 业务服务参考初始配置

yaml 复制代码
spring:
  datasource:
    hikari:
      maximum-pool-size: 12
      minimum-idle: 4
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000

虚拟线程场景建议:业务线程是虚拟线程,Hikari 连接池依旧需要合理限制,不能放任膨胀。

五、SpringBoot3 + JDK17 环境故障排查手段

  1. 日志关键词:PoolTimeoutException,重点检索连接拿不到异常栈;
  2. 开启 Actuator,监控 hikari 相关 metrics,观察活跃连接、等待队列;
  3. MySQL show processlist查看长时间运行会话;
  4. 代码审计:是否手动获取 Connection,资源是否闭环释放;
  5. 检查@Transactional内部是否存在 RPC、HTTP、大 IO;
  6. 如果开启虚拟线程,核对虚拟线程数量和数据库连接池配比。

六、生产编码 CheckList(适配 JDK17+SpringBoot3)

  • ✅连接池maximum‑pool‑size,CPU*2+IO 数仅作为初始参考,不是固定值;上线结合监控指标调优,同时兼顾 MySQL 总连接数与服务实例数;
  • ✅事务内部严禁 RPC、Feign 调用、大文件 IO、重量级计算;耗时逻辑移出事务,必要时队列异步;必须原子性的数据库操作保留短事务,不盲目队列化;
  • ✅业务代码尽量不手动获取Connection,交给 MyBatis 等框架管理连接生命周期;
  • ✅迫不得已手动操作连接时,先完成【获取‑异常‑回收】闭环,再写业务逻辑;优先try‑with‑resources或者回调模板,业务写进回调,防止分支逻辑绕过资源回收;
  • ✅开启 Actuator 监控 Hikari 连接池指标,线上配置活跃连接阈值告警;
  • ✅使用 JDK21 虚拟线程,不要误以为虚拟线程可以无视数据库连接池上限;
  • ✅升级 SpringBoot3/JDK17,不要直接照搬 JDK8 时代全套连接池配置,重新做压测验证。

七、AI 代码的坑

AI 生成数据库操作 Demo,经常只写正常流程,忽略异常、提前 return 场景。 JDK17 虽然资源关闭能力增强,但不会自动修复业务代码的资源泄露。复制 AI 数据库相关代码,务必审查资源回收闭环。

升级 SpringBoot3 + JDK17 会带来虚拟线程、模块化、性能提升,但不会自动修复长事务、连接泄露、SQL 慢查询这类业务问题 。 遇到could not get JDBC Connection报错,不要上来就调大最大连接数。优先排查:连接泄露、长事务、慢 SQL、线程模型与连接池的匹配,开启虚拟线程后要额外注意连接池约束。

我是十年 Java 后端程序媛,分享 JDK 迁移、业务踩坑实录,完整文章合集同步更新公众号:Java 后端程序媛手记。

下一篇分享 SpringBoot3 虚拟线程与数据库连接池配合实战。 如果对你有帮助,欢迎点赞收藏,关注我不错过后续更新。

相关推荐
二十雨辰1 小时前
[Java]-JVM面试题
java·开发语言·jvm
cfm_29141 小时前
ConcurrentHashMap 线程安全机制与 JDK 1.8 演进
java·开发语言·安全
tqs_123451 小时前
值传递与引用传递
java·开发语言·python
2601_962181961 小时前
Spring boot从0到1 - day01
java·spring boot·后端
典典分享指南1 小时前
短视频分镜提示词:让 AI 出片的产品口播
java·c#·bash·composer·symfony
IT_Octopus1 小时前
G1 与 GC 日志入门课程(以本次离线菜单 Full GC 事故为教材)
java·jvm
cfm_29141 小时前
ReentrantLock 中 lock 与 tryLock 核心区别
java
.Hypocritical.2 小时前
Tomcat本地部署+远程服务器部署超详细教程
java·服务器·tomcat
码视野2 小时前
基于 Spring Boot + Vue3 的【智慧公厕微负压排风除臭与客流人感空间占用导引中台】设计与实现(含PRD/三端高保真源码/大屏)
前端·vue.js·人工智能·spring boot·后端