StackTraces
Spring Boot已驗證

Spring Boot HikariPool Connection is not available

@debug_master32 天前更新閱讀 6 min00

報 HikariPool-1 - Connection is not available 基本不是數據庫掛了,是連接池被借空——有程式碼拿了連接不還,或者事務裡包著慢 I/O 長時間佔著連接。開 leak-detection-threshold 能把洩漏點連同調用棧打出來。

Issue & Env(案發現場與環境)

  • 業務場景:併發一上來,部分請求開始報連接超時;輕的隻影響個別接口,重的整個應用所有用到 DB 的接口都無響應。
  • 環境:HikariCP(Spring Boot 2.x / 3.x 預設連接池),connectionTimeout 預設 30000ms(30 秒)。HikariCP 3.4.5 與 5.1.0 下均有 Connection is not available 的案例。

StackTrace(堆疊日誌)

請求等滿 30 秒拿不到連接,拋出的標準例外:

java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms.
	at com.zaxxer.hikari.pool.HikariPool.createTimeoutException(HikariPool.java:696)
	at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:197)
	at com.zaxxer.hikari.pool.HikariPool.getConnection(HikariPool.java:186)
	at org.springframework.jdbc.datasource.DataSourceUtils.fetchConnection(DataSourceUtils.java:157)
	at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:124)
	at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:352)
	at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:465)
	at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:480)
	at org.springframework.jdbc.core.JdbcTemplate.queryForList(JdbcTemplate.java:573)
	at com.example.service.LeakService.doQuery(LeakService.java:42)
	at com.example.controller.OrderController.listOrders(OrderController.java:68)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:1004)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:750)

壓到池底時日誌會顯示連接數全部耗盡:

HikariPool-1 - Connection is not available, request timed out after 30000ms (total=0, active=0, idle=0, waiting=3)

total=0, active=0, idle=0, waiting=3 表示池裡一個可用連接都沒有,3 個執行緒在排隊乾等。

Reproduce(復現程式碼)

寫一個故意不歸還連接的程式碼,把池耗光。預設 maximumPoolSize 是 10:

@Service
public class LeakService {
    private final JdbcTemplate jdbcTemplate;

    public LeakService(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    // 借出连接后不关闭,模拟泄漏
    public void leak() throws SQLException {
        jdbcTemplate.getDataSource().getConnection(); // 用完不 close()
    }
}

併發調 leak() 超過池大小後,後續任何請求都會在 getConnection() 處卡滿 30 秒,然後拋上面那段超時例外。

Root Cause(根因分析)

HikariCP 的連接池大小是固定的(maximum-pool-size,預設 10)。每個執行緒執行 SQL 前要先 borrow 一個連接,用完了 return 回去。如果某個方法借了連接卻從不歸還,或者事務遲遲不結束、連接一直被執行緒佔著,池子就被慢慢掏空。

掏空後,新來的 getConnection() 只能排隊等空閒連接,等到 connectionTimeout(預設 30 秒)還是等不到,就拋出 SQLTransientConnectionException。這種故障有傳染性:一個接口洩漏連接,量一大,別的純讀接口也會因為拿不到連接而被拖垮,最終整站雪崩。

最常見的洩漏來源是兩類:

  1. 連接借了不還:手寫 getConnection() 卻不用 try-with-resources 關閉。
  2. @Transactional 裡幹慢活:事務全程持有連接,方法裡又去調外部 HTTP 接口、發消息或做耗時計算,連接被白白佔著幾十秒。這是生產裡最隱蔽的一種,因為程式碼看起來「沒有一行忘關連接」。

HikariCP issue #2161、#2374 裡還出現了另一種形態:網絡抖動後連接總數掉到 0 且不自動重建,需重啟應用。這種屬於池的自愈邊界,根因在連接創建執行緒卡在 socket 讀操作上,和普通洩漏是兩條線,不要混為一談。

The Fix(解決方案)

第一步:開啟洩漏檢測,把元兇揪出來

spring:
  datasource:
    hikari:
      leak-detection-threshold: 5000   # 连接被借走超过 5 秒未归还就告警

開啟後,洩漏的連接會在超時點打印調用棧,直接定位到是哪一行借的:

WARN com.zaxxer.hikari.pool.ProxyLeakTask - Connection leak detection triggered for connection org.postgresql.jdbc.PgConnection@1234abcd, stack trace follows
java.lang.Exception: Apparent connection leak detected
	at com.example.service.PaymentService.callExternalPaymentGateway(PaymentService.java:85)

第二步:修程式碼

借出來的連接用 try-with-resources 自動歸還:

try (Connection conn = jdbcTemplate.getDataSource().getConnection()) {
    // 用 conn
}

@Transactional 裡別做外網調用、重計算這類慢活,把外部 I/O 挪到事務外:

// 先做外部调用,再开事务落库
PaymentResult result = paymentClient.pay(request);
orderService.saveOrderWithResult(request, result);

第三步:按需調池參數

確認不是洩漏、單純併發高,再調大池子或縮短等待時間:

spring:
  datasource:
    hikari:
      maximum-pool-size: 30
      connection-timeout: 10000
      keepalive-time: 30000

調大池子只是緩衝,治標不治本。池子越大,單個連接空轉和數據庫連接資源佔用越多;真正的洩漏不修,多大池都會漏光。

References(參考來源)

最後更新於 2026-09-09

這篇幫到你了嗎?

剛解決了一個棘手的報錯?花兩分鐘記錄下來,幫助下一個遇到同樣問題的開發者。

貢獻一條解法