前言

身為後端工程師,你是否遇過這樣的情況:

  • 系統突然變得超級慢
  • 網頁一直轉圈圈
  • 收到「已超過連線逾時的設定」錯誤訊息

如果有,很可能就是連線池爆掉了。以下分享一個真實案例,從診斷、緊急處理到根治。

問題診斷

當你遇到以下錯誤訊息時,很可能是連線池出問題了:

1
System.InvalidOperationException: '已超過連線逾時的設定。在取得集區連線之前超過逾時等待的時間,可能的原因為所有的共用連線已在使用中,並已達共用集區大小的最大值。'

快速診斷方法

首先,用以下 SQL 查看目前的連線狀況。sys.sysprocesses 是 SQL Server 2000 相容性視圖,SQL Server 2005 起已標記 deprecated,這裡改用官方建議的 DMV:

1
2
3
4
5
6
7
8
9
10
11
12
-- 各資料庫連線數摘要(依登入帳號、狀態分組)
SELECT
DB_NAME(s.database_id) AS DatabaseName,
COUNT(*) AS NumberOfConnections,
s.login_name AS LoginName,
s.status,
s.host_name AS HostName,
s.program_name AS ProgramName
FROM sys.dm_exec_sessions AS s
WHERE s.database_id > 0
AND s.is_user_process = 1 -- 排除系統內部 session
GROUP BY s.database_id, s.login_name, s.status, s.host_name, s.program_name;

緊急處理方案

發現問題後,可以使用以下步驟處理:

  1. 檢視問題連線
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    -- 1. 查看該資料庫的連線詳情(SQL Server 2005+ DMV)
    SELECT
    s.session_id AS '處理序 ID',
    DB_NAME(s.database_id) AS '資料庫',
    s.login_name AS '登入名稱',
    s.host_name AS '主機名',
    s.program_name AS '程式名稱',
    s.login_time AS '登入時間',
    s.last_request_start_time AS '最後執行時間',
    s.status AS '狀態'
    FROM sys.dm_exec_sessions AS s
    WHERE s.database_id = DB_ID('資料庫名稱') -- 替換成你的資料庫名
    AND s.is_user_process = 1
    ORDER BY s.login_time DESC;

    -- 2. 查看具體在執行什麼 SQL
    SELECT
    s.session_id,
    r.blocking_session_id AS '被阻塞源',
    s.host_name AS '主機名',
    s.program_name AS '程式名稱',
    r.command AS '指令',
    r.cpu_time AS 'CPU時間',
    r.reads + r.writes AS '實體IO',
    r.start_time AS '開始執行時間',
    t.[text] AS 'SQL內容'
    FROM sys.dm_exec_sessions AS s
    JOIN sys.dm_exec_requests AS r ON s.session_id = r.session_id
    OUTER APPLY sys.dm_exec_sql_text(r.sql_handle) AS t
    WHERE s.database_id = DB_ID('資料庫名稱') -- 替換成你的資料庫名
    AND s.is_user_process = 1;

    -- 3. 殺掉特定連線(請小心使用)
    -- KILL 52; -- 替換成你要關閉的 session_id

    -- 4. 如果確定要關閉所有使用者連線,可以使用動態 SQL(需要特別小心)
    -- SQL Server 保留 session_id ≤ 50 給系統內部進程,所以只 KILL > 50 的
    DECLARE @kill varchar(8000) = '';
    SELECT @kill = @kill + 'KILL ' + CONVERT(varchar(5), s.session_id) + ';'
    FROM sys.dm_exec_sessions AS s
    WHERE s.database_id = DB_ID('資料庫名稱') -- 替換成你的資料庫名
    AND s.is_user_process = 1;

    -- 執行前先確認要關閉的連線
    PRINT @kill;

    -- 如果確認無誤,才可以執行(請小心使用)
    -- EXEC(@kill);

    -- 5. ⚠️ 高風險操作:切換到單一用戶模式
    -- 警告:這不是連線池爆掉的正確解法。
    -- SET SINGLE_USER 會立即 rollback 所有進行中的交易、強制踢掉全部連線,
    -- 造成整個資料庫暫時無法服務(生產環境等同短暫停機)。
    -- 僅適用於資料庫維護(如 DBCC CHECKDB)等明確需要獨占存取的場景,
    -- 連線池問題應從應用端未釋放連線的根因下手,而非用此指令強制清場。
    ALTER DATABASE 資料庫名稱
    SET SINGLE_USER
    WITH ROLLBACK IMMEDIATE;

    -- 完成後務必切換回多用戶模式,否則所有應用連線將持續被拒絕
    -- ALTER DATABASE 資料庫名稱 SET MULTI_USER;

程式碼優化建議

這次的根因是前人寫的 VB 程式碼沒有正確釋放資料庫連線。以下是修正方向:

  1. 使用 Using 區塊確保資源釋放

    1
    2
    3
    Using connection As New SqlConnection(connectionString)
    ' 資料庫操作
    End Using
  2. 避免全域宣告資料庫連線

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ' 不建議
    Dim conn As New SqlConnection '全域變數

    ' 建議
    Private Function GetData() As DataTable
    Using conn As New SqlConnection(connectionString)
    ' 程式碼
    End Using
    End Function
  3. 設定適當的連線池參數

    1
    2
    3
    4
    5
    6
    7
    <connectionStrings>
    <add name="MyConnection"
    connectionString="Server=myserver;Database=mydb;
    Max Pool Size=100;Min Pool Size=5;
    Connection Timeout=30;"
    providerName="System.Data.SqlClient" />
    </connectionStrings>

Max Pool Size 預設值就是 100,直接填 100 等於沒改。實際調整依據:若應用有高峰期 N 個 thread 同時存取資料庫,Pool Size 設成接近 N;設太大反而讓 SQL Server 同時承受過多連線,效能反降。調整前先用上面的 DMV 查看實際峰值再決定。

注意: System.Data.SqlClient 是 .NET Framework 內建的舊版 provider,已進入維護模式。新專案或 .NET Core/.NET 5+ 環境,建議改用 NuGet 套件 Microsoft.Data.SqlClient,微軟已將後者列為主動開發的標準驅動。舊系統維護沿用舊 provider 沒問題,但新專案不建議照搬這段連線字串。

預防措施

監控:SQL Server Agent 可設定 Job 定期執行上方的 DMV 查詢,連線數超過門檻(例如 > 80)就寄信或寫事件日誌。門檻值參考 Max Pool Size,建議設在 80% 左右就開始警示,留緩衝時間處理。

程式碼審查重點SqlConnection 必須在 Using 區塊或 try/finally 中確保 Dispose()。容易漏的地方是早期 VB 程式的全域連線變數、以及 DataAdapter.Fill() 後沒關連線的寫法。

重試機制:瞬間高峰時連線池滿是短暫現象,可在取連線失敗時加入指數退避重試(最多 3 次,每次間隔 200–500ms);持續滿才代表有連線洩漏。

參考資源