問題:
應用程序建立的數據庫連接,也就是非交互式連接 是什麼時候開始的?什麼時候結束?連接結束後 會影響應用程序操作db失敗嗎? 還有就是如果連接關閉了 會立馬重新建立新的連接嗎?
解答:
我們討論的是應用程序通過連接池(如HikariCP)與MySQL建立的連接。這些連接是非交互式連接。
- 連接何時開始?當應用程序從連接池請求一個連接時,如果連接池中沒有可用的空閒連接,則會新建一個連接。這個連接建立的時刻就是開始。
- 連接何時結束?當連接被關閉(返回到連接池)或者因為超時(如wait_timeout)被服務器關閉時,連接結束。
- 連接結束後,如果應用程序嘗試使用這個已經關閉的連接來操作數據庫,就會失敗,會拋出異常(如CommunicationsException)。
- 如果連接被關閉,應用程序再次請求連接時,連接池會嘗試重新建立新的連接(如果連接池中有空閒連接且未關閉,則使用空閒連接;如果沒有,則新建)。
但是,注意連接池通常會有一些機制來檢測連接的有效性,比如在將連接交給應用程序之前,可能會執行一個測試查詢(如SELECT 1)來驗證連接是否還有效。如果連接已經無效,連接池會丟棄這個連接並新建一個。
所以,當MySQL服務器因為wait_timeout關閉了連接,而連接池並不知道,如果連接池試圖將這個已經關閉的連接分配給應用程序,應用程序就會操作失敗。為了避免這種情況,連接池通常會有驗證機制。
下面詳細解釋:
連接的生命週期:
- 創建:當連接池需要新連接時,會創建新的數據庫連接。
- 使用:應用程序從連接池獲取連接,執行SQL,然後歸還連接。
- 關閉:連接池可以主動關閉連接(例如,連接空閒時間超過連接池設置的空閒超時,或者連接存活時間超過最大生命週期),或者因為數據庫服務器關閉了連接(如wait_timeout)而被動關閉。
當數據庫服務器因為wait_timeout關閉了連接,這個連接在連接池中就變成了一個無效的連接。如果連接池沒有驗證機制,應用程序拿到這個無效連接就會報錯。
因此,連接池通常會有以下配置來避免這種情況:
- 驗證查詢(validationQuery):例如,一個簡單的SQL語句(如SELECT 1)用來在連接被取出前驗證其有效性。
- 測試連接的有效性(testOnBorrow):在將連接交給應用程序之前,執行驗證查詢。如果驗證失敗,則丟棄該連接並嘗試獲取另一個連接。
- 空閒連接驗證(testWhileIdle):在連接空閒時,定期執行驗證查詢,確保空閒連接仍然是有效的。
- 最大生命週期(maxLifetime):連接在連接池中的最長存活時間,即使數據庫的wait_timeout很長,連接池也會在達到最大生命週期後關閉連接,然後新建連接,這樣可以避免因為數據庫連接長時間空閒而被服務器關閉。
所以,如果連接關閉了,應用程序操作DB會失敗,但連接池機制可以儘量減少這種情況,並在連接失效時重新建立新的連接。
總結:
- 連接由連接池創建,並在需要時分配給應用程序。
- 連接結束的原因包括:主動關閉(連接池因超時或生命週期結束)或被動關閉(數據庫服務器因超時關閉)。
- 連接結束後,如果應用程序使用該連接,會失敗。
- 連接池通過驗證機制可以檢測到無效連接並重新建立新連接,從而避免應用程序失敗。
但是,如果連接池沒有配置驗證,或者驗證不夠及時,應用程序可能會拿到已經關閉的連接,從而導致操作失敗。因此,合理配置連接池的參數非常重要。
數據庫連接的生命週期
連接的開始和結束
連接開始:當應用程序從連接池獲取連接或直接創建新連接時
// 連接開始的時刻
Connection conn = dataSource.getConnection(); // 連接建立
// 或者
Connection conn = DriverManager.getConnection(url, user, password);
連接結束:
- 主動關閉:應用程序顯式調用
conn.close() - 超時關閉:MySQL服務器的
wait_timeout到達 - 連接池回收:連接池因空閒超時或最大生命週期回收連接
- 網絡異常:網絡中斷或數據庫服務器重啓
連接關閉對應用程序的影響
會影響應用程序操作DB失敗嗎? - 會的!
try {
// 獲取連接
Connection conn = dataSource.getConnection();
// 假設這個連接在接下來10分鐘空閒後被MySQL關閉
Thread.sleep(600000);
// 嘗試使用已關閉的連接 - 會失敗!
Statement stmt = conn.createStatement(); // 可能拋出異常
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
} catch (SQLException e) {
// 捕獲類似:Communications link failure 的異常
System.out.println("操作失敗: " + e.getMessage());
}
連接池的工作機制
連接關閉後的重新建立
連接關閉後會立即重新建立嗎? - 取決於連接池的配置和實現
// HikariCP 配置示例
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("user");
config.setPassword("password");
// 關鍵配置參數
config.setMinimumIdle(5); // 最小空閒連接數
config.setMaximumPoolSize(20); // 最大連接數
config.setIdleTimeout(300000); // 連接空閒超時(5分鐘)
config.setMaxLifetime(1800000); // 連接最大生命週期(30分鐘)
config.setConnectionTimeout(30000); // 獲取連接超時時間
config.setValidationTimeout(5000); // 驗證連接超時
// 連接有效性驗證
config.setConnectionTestQuery("SELECT 1");
config.setLeakDetectionThreshold(60000);
HikariDataSource dataSource = new HikariDataSource(config);
連接池的自動恢復機制
// 當應用程序請求連接時,連接池的行為:
public Connection getConnection() throws SQLException {
// 1. 首先嚐試從空閒連接池獲取
Connection conn = borrowFromIdlePool();
if (conn != null) {
// 2. 驗證連接是否有效
if (!isConnectionValid(conn)) {
// 連接已失效,關閉並創建新連接
closeConnection(conn);
conn = createNewConnection();
}
return conn;
}
// 3. 如果沒有空閒連接且未達上限,創建新連接
if (currentPoolSize < maxPoolSize) {
return createNewConnection();
}
// 4. 等待其他連接釋放或超時
return waitForAvailableConnection();
}
實際場景分析
場景1:連接被MySQL關閉後的恢復
// 應用程序代碼
@Service
public class UserService {
@Autowired
private DataSource dataSource;
public User getUserById(Long id) {
// 獲取連接時,連接池會檢查連接有效性
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?")) {
stmt.setLong(1, id);
try (ResultSet rs = stmt.executeQuery()) {
if (rs.next()) {
return mapResultSetToUser(rs);
}
}
} catch (SQLException e) {
// 如果連接失效,這裏會捕獲異常
// 但連接池會在下次請求時自動創建新連接
logger.error("查詢用户失敗", e);
throw new RuntimeException("數據庫操作失敗", e);
}
return null;
}
}
場景2:連接池的完整工作流程
最佳實踐配置
連接池配置建議
# HikariCP 推薦配置
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.idle-timeout=300000 # 5分鐘 < MySQL的wait_timeout
spring.datasource.hikari.max-lifetime=1800000 # 30分鐘
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.validation-timeout=5000
spring.datasource.hikari.leak-detection-threshold=60000
# MySQL 服務器配置建議
# wait_timeout = 600 # 10分鐘
# interactive_timeout = 1800 # 30分鐘
監控和診斷
@Component
public class ConnectionPoolMonitor {
@Autowired
private HikariDataSource dataSource;
@Scheduled(fixedRate = 60000) // 每分鐘監控一次
public void monitorConnectionPool() {
HikariPoolMXBean pool = dataSource.getHikariPoolMXBean();
logger.info("連接池狀態 - 活躍連接: {}, 空閒連接: {}, 等待線程: {}, 總連接: {}",
pool.getActiveConnections(),
pool.getIdleConnections(),
pool.getThreadsAwaitingConnection(),
pool.getTotalConnections());
}
}
總結
- 連接開始:應用程序從連接池獲取連接時
- 連接結束:主動關閉、超時關閉、連接池回收或網絡異常
- 影響:連接關閉後繼續使用會導致操作失敗
- 重新建立:連接池會自動檢測失效連接並在下次請求時創建新連接
- 關鍵:合理配置連接池參數,確保連接有效性檢查和自動恢復機制正常工作
通過正確的連接池配置,應用程序可以透明地處理連接的失效和重建,保證系統的穩定性和可靠性。