驅動程式 go-mssqldb 使用 Go database/sql 套件內建的連線池。 每個 sql.DB 實例都會維持一個閒置連線池,這些連線會自動重複使用。 本文說明如何根據你的工作負載配置池。
游泳池運作原理
當你呼叫 db.QueryContext、 或 db.ExecContext任何其他資料庫方法時:
- 池子會嘗試尋找閒置連線。
- 若無閒置連線且池容量未達最大,則會建立新連線。
- 若池容量已達最大,通話會阻塞,直到連線可用。
- 操作完成後,連線會回傳給池。
集區組態方法
使用 *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)
把這些數值當作起點,而不是普遍的預設值。 對許多服務來說,第一個有用的步驟是先將 MaxOpenConns 與 MaxIdleConns 設為有界,然後只有在部署方式可能導致留下陳舊連線或連線分布不均時,才加入存留期與閒置限制。
建議的設定
| 劇本 | 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 層級的連線上限。 超過伺服器最大同時連線數會導致所有用戶端登入失敗。
較短的 ConnMaxLifetime 和 ConnMaxIdleTime 值可降低在故障轉移或閘道重新啟動後出現過時連線的機率,但也會增加連線的頻繁變動。 如果你的應用程式直接連線到穩定的 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 為有界值。 |
健康檢查與失效連線偵測
池子不會主動驗證閒置連線。 伺服器回收時閒置的連線在下一次使用時會失敗。 設定 ConnMaxLifetime 並 ConnMaxIdleTime 輪換連接,避免它們變舊:
// 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,以驗證連線。 |