問題:

應用程序建立的數據庫連接,也就是非交互式連接 是什麼時候開始的?什麼時候結束?連接結束後 會影響應用程序操作db失敗嗎? 還有就是如果連接關閉了 會立馬重新建立新的連接嗎?

 

解答:

我們討論的是應用程序通過連接池(如HikariCP)與MySQL建立的連接。這些連接是非交互式連接。

  1. 連接何時開始?當應用程序從連接池請求一個連接時,如果連接池中沒有可用的空閒連接,則會新建一個連接。這個連接建立的時刻就是開始。
  2. 連接何時結束?當連接被關閉(返回到連接池)或者因為超時(如wait_timeout)被服務器關閉時,連接結束。
  3. 連接結束後,如果應用程序嘗試使用這個已經關閉的連接來操作數據庫,就會失敗,會拋出異常(如CommunicationsException)。
  4. 如果連接被關閉,應用程序再次請求連接時,連接池會嘗試重新建立新的連接(如果連接池中有空閒連接且未關閉,則使用空閒連接;如果沒有,則新建)。

但是,注意連接池通常會有一些機制來檢測連接的有效性,比如在將連接交給應用程序之前,可能會執行一個測試查詢(如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);

連接結束:

  1. 主動關閉:應用程序顯式調用conn.close()
  2. 超時關閉:MySQL服務器的wait_timeout到達
  3. 連接池回收:連接池因空閒超時或最大生命週期回收連接
  4. 網絡異常:網絡中斷或數據庫服務器重啓

連接關閉對應用程序的影響

會影響應用程序操作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:連接池的完整工作流程

數據庫的發展史_TCancel的博客-博客_生命週期

 

最佳實踐配置

連接池配置建議

# 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());
    }
}

 

總結

  1. 連接開始:應用程序從連接池獲取連接時
  2. 連接結束:主動關閉、超時關閉、連接池回收或網絡異常
  3. 影響:連接關閉後繼續使用會導致操作失敗
  4. 重新建立:連接池會自動檢測失效連接並在下次請求時創建新連接
  5. 關鍵:合理配置連接池參數,確保連接有效性檢查和自動恢復機制正常工作

通過正確的連接池配置,應用程序可以透明地處理連接的失效和重建,保證系統的穩定性和可靠性。