在 Spring + MyBatis + C3P0 + MySQL 技術棧中,資料庫連接池配置不當導致的 wait_timeout 斷連是經典痛點。本文提供 C3P0 完整配置參數詳解、Spring 整合範例,並深度剖析 CommunicationsException: wait_timeout 根因與最佳解決方案。
| 參數 | 預設值 | 說明 | 生產建議 |
|---|---|---|---|
acquireIncrement | 3 | 連接耗盡時一次新增的連接數 | 5-10 |
acquireRetryAttempts | 30 | 獲取連接失敗重試次數 | 30 |
acquireRetryDelay | 1000 | 重試間隔毫秒 | 1000 |
autoCommitOnClose | false | 關閉連接時回滾未提交事務 | false |
automaticTestTable | null | 建立測試表名,優於 preferredTestQuery | C3P0_TEST |
breakAfterAcquireFailure | false | 獲取失敗後標記 DataSource 失效 | false |
checkoutTimeout | 0 | 等待連接超時毫秒 (0=無限等待) | 3000-5000 |
idleConnectionTestPeriod | 0 | 檢查空閒連接間隔秒數 關鍵參數 | < wait_timeout |
initialPoolSize | 3 | 初始化連接數 | minPoolSize 同值 |
maxIdleTime | 0 | 連接最大空閒秒數 (0=永不丟棄) | 1800-3600 |
maxPoolSize | 15 | 最大連接數 | 視應用並發而定 |
maxStatements | 0 | 全域 Statement 快取數 | 100-500 |
numHelperThreads | 3 | 異步操作輔助執行緒數 | 3-10 |
preferredTestQuery | null | 連接測試 SQL (需表預先存在) | SELECT 1 |
testConnectionOnCheckin | false | 歸還連接時測試 | true |
testConnectionOnCheckout | false | 取出連接時測試 (效能影響大) | false |
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
<!-- 關鍵優化參數 -->
<property name="initialPoolSize" value="10"/>
<property name="minPoolSize" value="10"/>
<property name="maxPoolSize" value="50"/>
<property name="acquireIncrement" value="5"/>
<property name="maxIdleTime" value="1800"/>
<property name="idleConnectionTestPeriod" value="18000"/> <!-- 5小時,小於 MySQL 8小時 -->
<property name="preferredTestQuery" value="SELECT 1"/>
<property name="testConnectionOnCheckin" value="true"/>
<property name="checkoutTimeout" value="3000"/>
</bean>
### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: The last packet successfully received from the server was 55,518,630 milliseconds ago. is longer than the server configured value of 'wait_timeout'. You should consider either expiring and/or testing connection validity before use in your application, increasing the server configured values for client timeouts, or using the Connector/J connection property 'autoReconnect=true' to avoid this problem.
mysql> SHOW GLOBAL VARIABLES LIKE 'wait_timeout'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | wait_timeout | 28800 | -- 預設 8 小時 (28800 秒) +---------------+-------+
錯誤日誌顯示連接閒置 **55,518,630 毫秒 ≈ 15.4 小時** 遠超過 8 小時預設值。MySQL 主動斷開閒置連接,但 C3P0 連接池仍持有這些失效連接,應用取出時拋出異常。
| 方案 | 可行性 | 缺點 |
|---|---|---|
| 增加 MySQL wait_timeout | ❌ 不推薦 | 最大 2147483 秒 (約 248 天),治標不治本;資源浪費 |
JDBC URL 加 autoReconnect=true | ❌ 強烈不建議 | MySQL 官方棄用,重連影響效能,可能導致事務不一致 |
| 連接池層面驗證/清理失效連接 | ✅ 最佳實踐 | 需正確配置 C3P0 測試機制 |
| 策略 | 觸發時機 | 效能影響 | 可靠度 | 建議 |
|---|---|---|---|---|
testConnectionOnCheckout | 取出連接時 | 高 (每次取出皆查詢) | 最高 | 極高可靠需求才啟用 |
testConnectionOnCheckin | 歸還連接時 | 低 (異步執行) | 高 | 推薦啟用 |
idleConnectionTestPeriod | 定期掃描空閒連接 | 極低 (背景執行緒) | 高 | 核心防禦機制,必須啟用 |
「大多數應用程序應該使用 idleConnectionTestPeriod 和 testConnectionOnCheckin 相當可靠。閒置的測試和登記測試是異步執行的,這可能導致更好的性能。」
<property name="idleConnectionTestPeriod" value="18000"/> <!-- 5小時 (18000秒) < 28800秒 --> <property name="testConnectionOnCheckin" value="true"/> <!-- 歸還時驗證 --> <property name="preferredTestQuery" value="SELECT 1"/> <-- 輕量級測試查詢 --> <property name="maxIdleTime" value="25200"/> <!-- 7小時主動回收,早於 MySQL 斷開 -->
class CheckIdleResourcesTask extends TimerTask {
public void run() {
synchronized (BasicResourcePool.this) {
checkIdleResources(); // 掃描空閒連接
}
}
}
由單一 TimerThread 定期執行,間隔由 idleConnectionTestPeriod 控制。
private void testPooledConnection(Object resc) throws Exception {
PooledConnection pc = (PooledConnection) resc;
Connection conn = pc.getConnection(); // 取得物理連接
// 選擇測試連接:有 Statement Cache 用代理連接,否則用物理連接
Connection testConn = (scache != null && testQuery != null) ? conn : pc.getPhysicalConnection();
int status;
if (testQuery == null) {
status = connectionTester.activeCheckConnection(testConn); // 無查詢模式
} else {
status = ((QueryConnectionTester) connectionTester).activeCheckConnection(testConn, testQuery);
}
// 根據狀態處理:OK 繼續,INVALID 拋出異常觸發重建
switch (status) {
case CONNECTION_IS_OKAY: break;
case CONNECTION_IS_INVALID: throw new SQLException("Connection is invalid");
...
}
}
SELECT 1 (或預設測試)<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<!-- 基礎連接資訊 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://host:3306/db?useUnicode=true&characterEncoding=UTF-8&autoReconnect=false"/>
<property name="user" value="app_user"/>
<property name="password" value="secure_password"/>
<!-- 池大小控制 -->
<property name="initialPoolSize" value="10"/>
<property name="minPoolSize" value="10"/>
<property name="maxPoolSize" value="100"/>
<property name="acquireIncrement" value="5"/>
<!-- 連接生命週期管理 (核心防斷連) -->
<property name="maxIdleTime" value="25200"/> <!-- 7小時 -->
<property name="idleConnectionTestPeriod" value="18000"/> <!-- 5小時 -->
<property name="testConnectionOnCheckin" value="true"/>
<property name="preferredTestQuery" value="SELECT 1"/>
<!-- 獲取連接控制 -->
<property name="checkoutTimeout" value="5000"/>
<property name="acquireRetryAttempts" value="30"/>
<property name="acquireRetryDelay" value="1000"/>
<!-- Statement 快取 -->
<property name="maxStatements" value="200"/>
<property name="maxStatementsPerConnection" value="10"/>
<!-- 異步執行緒 -->
<property name="numHelperThreads" value="5"/>
</bean>
SHOW PROCESSLIST 觀察 Sleep 連接是否被定期清理wait_timeout 斷連本質是連接池持有失效連接。解決核心在於:
idleConnectionTestPeriod + testConnectionOnCheckin 雙重機制maxIdleTime 早於 MySQL 斷開時間SELECT 1 避免業務表鎖定正確配置後,C3P0 能在 MySQL 主動斷開前預先清理失效連接,確保應用取出的連接永遠有效,徹底消除 CommunicationsException。