使用 go-mssqldb 的連線集區

驅動程式 go-mssqldb 使用 Go database/sql 套件內建的連線池。 每個 sql.DB 實例都會維持一個閒置連線池,這些連線會自動重複使用。 本文說明如何根據你的工作負載配置池。

游泳池運作原理

當你呼叫 db.QueryContext、 或 db.ExecContext任何其他資料庫方法時:

  1. 池子會嘗試尋找閒置連線。
  2. 若無閒置連線且池容量未達最大,則會建立新連線。
  3. 若池容量已達最大,通話會阻塞,直到連線可用。
  4. 操作完成後,連線會回傳給池。

集區組態方法

使用 *sql.DB 上的方法設定集區:

方法 Description
db.SetMaxOpenConns(n) 最大開放連接數(使用中+閒置)。 預設值: 0 (無限)。
db.SetMaxIdleConns(n) 池中閒置連線的最大數量。 預設值:2
db.SetConnMaxLifetime(d) 連線可重複使用的最大總時間。 預設值: 0 (無限制)。
db.SetConnMaxIdleTime(d) 連線在關閉前可維持閒置的最長時間。 預設值: 0 (無限制)。

範例

開啟資料庫後立即設定池:

db, err := sql.Open("sqlserver", connString)
if err != nil {
    log.Fatal(err)
}

db.SetMaxOpenConns(25)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(5 * time.Minute)
db.SetConnMaxIdleTime(1 * time.Minute)

把這些數值當作起點,而不是普遍的預設值。 對許多服務來說,第一個有用的步驟是先將 MaxOpenConnsMaxIdleConns 設為有界,然後只有在部署方式可能導致留下陳舊連線或連線分布不均時,才加入存留期與閒置限制。

劇本 MaxOpen MaxIdle MaxLifetime 最大閒置時間
穩定 SQL Server 網路路徑上的網頁應用程式 25 10 0 0
透過 Azure SQL、閘道器或負載平衡器的網頁應用程式 25 10 5 分鐘 1分鐘
高吞吐量服務 50-100 25 5 分鐘 30 秒
背景作業 / 命令列工具 5 2 0 0
Azure SQL Database (Basic/Standard) 10-20 5 5 分鐘 1分鐘

小提示

設定MaxOpenConns低於你 SQL Server 實例或 Azure SQL 層級的連線上限。 超過伺服器最大同時連線數會導致所有用戶端登入失敗。

較短的 ConnMaxLifetimeConnMaxIdleTime 值可降低在故障轉移或閘道重新啟動後出現過時連線的機率,但也會增加連線的頻繁變動。 如果你的應用程式直接連線到穩定的 SQL Server 執行個體,而且沒有出現過時連線失敗,將這兩個值都維持為 0 是合理的。

監控集區統計資料

可用 db.Stats() 來閱讀目前的泳池統計:

stats := db.Stats()
fmt.Printf("Open: %d, InUse: %d, Idle: %d\n",
    stats.OpenConnections, stats.InUse, stats.Idle)
fmt.Printf("WaitCount: %d, WaitDuration: %v\n",
    stats.WaitCount, stats.WaitDuration)

主要領域:

Field Description
OpenConnections 總開啟連線數(使用中 + 閒置)。
InUse 目前來電者已查斷的連線。
Idle 人脈在泳池裡等著。
WaitCount 通話者必須等待連線的總次數。
WaitDuration 總累積等待時間。

如果 WaitCount 是穩定成長,不要自動增加 MaxOpenConns 。 首先確認資料列、交易和專用連線是否已及時關閉,並確認伺服器能支援更大的連線集區。

SessionInitSQL

使用 SessionInitSQL,在每個新連線進入連線池時執行 SQL 陳述式。 此功能對於設定會話層級選項非常有用:

import (
    "database/sql"
    "github.com/microsoft/go-mssqldb"
    "github.com/microsoft/go-mssqldb/msdsn"
)

config := msdsn.Config{
    Host:     "<server>",
    Port:     1433,
    Database: "AdventureWorks2025",
}

connector := mssql.NewConnectorConfig(config)
connector.SessionInitSQL = "SET ANSI_NULLS ON; SET QUOTED_IDENTIFIER ON"
db := sql.OpenDB(connector)

連線固定

某些操作會將連線釘住,直到操作結束前不會返回到池中:

  • 交易 (db.BeginTx) - 連線會固定至呼叫 Commit()Rollback() 為止。
  • 單一連線 (db.Conn) - 連線會固定,直到呼叫 conn.Close() 為止。
  • 開啟的資料列 (db.QueryContext) - 連線會固定,直到呼叫 rows.Close() 為止。

務必及時關閉這些資源,以免資源池耗盡。

偵測並解決集區資源耗盡

當所有連線都在使用中,且連線池已達到 MaxOpenConns 時,就會發生連線池耗盡。 新來電者會封鎖,直到連線回傳。 症狀包括高延遲、goroutine 堆積,以及最終出現的 context 截止期限錯誤。

監控疲勞狀況

定期輪詢 db.Stats() 並在偵測到爭議時提醒:

func monitorPool(ctx context.Context, db *sql.DB, interval time.Duration) {
    ticker := time.NewTicker(interval)
    defer ticker.Stop()

    var lastWaitCount int64
    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            stats := db.Stats()
            newWaits := stats.WaitCount - lastWaitCount
            lastWaitCount = stats.WaitCount

            if newWaits > 0 {
                log.Printf("POOL CONTENTION: %d new waits, avg wait %v, open=%d, inUse=%d, idle=%d",
                    newWaits, stats.WaitDuration/time.Duration(stats.WaitCount),
                    stats.OpenConnections, stats.InUse, stats.Idle)
            }
        }
    }
}

常見原因和解決方案

原因 癥狀 解決方法
MaxOpenConns 對於工作量而言太低了 WaitCount 穩定成長。 增加 MaxOpenConns
錯誤路徑中未封閉的列 InUse 增加,而 Idle 維持在 0。 請在 QueryContext 之後立即使用 defer rows.Close()
長時間執行的交易 InUse 維持在高位。 保持交易簡潔。 使用內容逾時機制。
不必要地使用 db.Conn InUse 比預期還要高。 只有在需要工作階段範圍的狀態(暫存資料表)時,才使用 db.Conn
MaxOpenConns 未設定(無限) 在負載下有數百個開啟的連線。 總是設定 MaxOpenConns 為有界值。

健康檢查與失效連線偵測

池子不會主動驗證閒置連線。 伺服器回收時閒置的連線在下一次使用時會失敗。 設定 ConnMaxLifetimeConnMaxIdleTime 輪換連接,避免它們變舊:

// Rotate connections every 5 minutes to stay compatible
// with load balancers and Azure SQL failover.
db.SetConnMaxLifetime(5 * time.Minute)

// Close connections that have been idle for over 1 minute
// to reduce the number of stale connections.
db.SetConnMaxIdleTime(1 * time.Minute)

Note

ConnMaxIdleTime 主動關閉閒置連線時,SQL Server 記錄中可能會顯示連線遭到中斷。 這是預期中的行為,不是連線外洩。 如果您的 DBA 報告意外連線關閉,請確認設定 ConnMaxIdleTime 是否符合團隊的監控期望。

如果您的應用程式是透過負載平衡器,或透過已啟用異地複寫的 Azure SQL 進行連線,請將 ConnMaxLifetime 設為 5 分鐘或更短。 此設定確保故障轉移後連線會重新分配至副本間。

啟動時驗證連線

打開資料庫後,務必呼叫 db.PingContext 以確認連線字串正確,且伺服器可連線:

db, err := sql.Open("sqlserver", connString)
if err != nil {
    log.Fatal(err)
}

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := db.PingContext(ctx); err != nil {
    log.Fatalf("Cannot connect to database: %v", err)
}

出口池指標

透過定期閱讀 db.Stats(),將池統計數據暴露給你的監控系統:

普羅米修斯的例子

註冊可追蹤集區統計資料並定期更新的量測指標:

import "github.com/prometheus/client_golang/prometheus"

var (
    dbOpenConns = prometheus.NewGauge(prometheus.GaugeOpts{
        Name: "db_open_connections",
        Help: "Number of open database connections.",
    })
    dbInUseConns = prometheus.NewGauge(prometheus.GaugeOpts{
        Name: "db_in_use_connections",
        Help: "Number of connections currently in use.",
    })
    dbWaitCount = prometheus.NewCounter(prometheus.CounterOpts{
        Name: "db_wait_count_total",
        Help: "Total number of times a caller waited for a connection.",
    })
    dbWaitDuration = prometheus.NewCounter(prometheus.CounterOpts{
        Name: "db_wait_duration_seconds_total",
        Help: "Total wait time for a connection.",
    })
)

func init() {
    prometheus.MustRegister(dbOpenConns, dbInUseConns, dbWaitCount, dbWaitDuration)
}

func recordPoolMetrics(ctx context.Context, db *sql.DB) {
    ticker := time.NewTicker(10 * time.Second)
    defer ticker.Stop()

    var lastWaitCount int64
    var lastWaitDuration time.Duration
    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            stats := db.Stats()
            dbOpenConns.Set(float64(stats.OpenConnections))
            dbInUseConns.Set(float64(stats.InUse))
            dbWaitCount.Add(float64(stats.WaitCount - lastWaitCount))
            dbWaitDuration.Add((stats.WaitDuration - lastWaitDuration).Seconds())
            lastWaitCount = stats.WaitCount
            lastWaitDuration = stats.WaitDuration
        }
    }
}

池組設定檢查清單

Area Recommendation
MaxOpenConns 總是設定為有界值。 將其設定為符合你的工作負載並行數,且低於伺服器的連線上限。
MaxIdleConns 設定為至少是 MaxOpenConns 的一半。 閒置連接太少會導致頻繁的重新連接負擔。
ConnMaxLifetime Azure SQL 或負載平衡環境設定為 5 分鐘。 防止失效連線累積。
ConnMaxIdleTime 設定在 30-60 秒內關閉不再需要的連線。
Monitoring 輪詢 db.Stats(),並針對 WaitCount 的增長發出警示。
資源清理 總是 defer rows.Close()defer tx.Rollback()defer conn.Close()
啟動驗證 sql.Open之後呼叫db.PingContext,以驗證連線。