Quarkus Panache高并发事务下JDBC连接获取失败及正确关闭方法
你遇到的Unable to acquire JDBC Connection本质是数据库连接池耗尽,核心原因是并发量远超连接池承载能力,加上事务边界、线程模型和数据库操作逻辑的不合理,导致连接无法及时归还。以下是针对性的解决方案:
1. 控制并发请求量
transformToUniAndMerge默认会无限制并行执行所有请求,1000个并发请求直接打满连接池(默认连接池大小通常只有10-20)。必须限制并发数,建议设置为和连接池最大容量一致:
return multi .onItem().transformToUniAndMerge(10, number -> { // 10对应连接池max-size,可根据配置调整 return Uni.createFrom() .deferred(() -> executeSomeTransaction(number)) .runSubscriptionOn(Infrastructure.getDefaultWorkerPool()); })
2. 优化事务边界,避免嵌套事务
你的代码存在事务嵌套问题:executeSomeTransaction已经标记了@Transactional,但updateItemNonUni又加了@Transactional(REQUIRED),这会导致事务上下文混乱,延长连接占用时间。直接去掉updateItemNonUni上的事务注解,复用外层事务即可。
3. 修复仓库更新逻辑,减少连接占用
原代码先findById再更新的方式不仅低效,还会延长连接持有时间。改用Panache的批量更新语句,直接在数据库层面完成操作:
@Override public Uni<Void> updateItem(Item item) { return Uni.createFrom().deferred(() -> { String updateQuery = "UPDATE Item SET balance = ?1, status = ?2, updatedDate = ?3 WHERE id = ?4"; return update(updateQuery, item.getBalance(), item.getStatus(), LocalDateTime.now(), item.getID()) .invoke(count -> { if (count == 0) { throw new NoSuchElementException("Item not found with id: " + item.getID()); } }) .replaceWithVoid(); }).runSubscriptionOn(Infrastructure.getDefaultWorkerPool()); }
同时去掉updateItem里的emitOn——非响应式Hibernate ORM的操作必须在Worker线程完成,切换回EventLoop只会打乱事务上下文,导致连接无法及时释放。
4. 调整连接池配置
在application.properties里优化连接池参数,匹配你的并发需求:
# 连接池最大容量,根据服务器和数据库性能调整 quarkus.datasource.jdbc.max-size=20 # 最小空闲连接数 quarkus.datasource.jdbc.min-size=5 # 空闲连接超时时间,及时回收闲置连接 quarkus.datasource.jdbc.idle-timeout=30s # 获取连接超时时间,避免无限等待 quarkus.datasource.jdbc.acquisition-timeout=5s
5. 简化线程模型
去掉executeSomeTransaction里的emitOn(MutinyHelper.executor(Vertx.currentContext()))——数据库操作完成后不需要切换回EventLoop,多余的线程切换会导致事务上下文丢失,连接无法正常归还到池子里。
总结
核心思路是控制并发量不超过连接池承载能力,同时优化事务和数据库操作逻辑,减少连接的占用时间,确保连接能及时归还到池子里。按上述调整后,连接耗尽的问题应该能得到解决。
内容的提问来源于stack exchange,提问作者Hzt

