go-mssqldbドライバーはGoのdatabase/sqlパッケージが提供する内蔵接続プールを利用しています。 すべての sql.DB インスタンスは、自動的に再利用されるアイドル接続のプールを維持しています。 この記事では、あなたのワークロードに合わせたプールの設定方法を説明します。
プールの仕組み
db.QueryContext、db.ExecContext、またはその他のデータベースメソッドを呼び出すとき:
- プールは使用されていない接続を見つけようとします。
- アイドル接続が利用できず、プールの最大サイズに達していない場合は、新しい接続が作成されます。
- プールが最大容量に達している場合、接続が利用可能になるまで通話がブロックされます。
- 操作完了後、接続はプールに戻されます。
プール構成方法
*sql.DBのメソッドを使ってプールを設定できます:
| Method | Description |
|---|---|
db.SetMaxOpenConns(n) |
最大開通接続数(使用中+アイドル)。 デフォルト: 0 (無制限)。 |
db.SetMaxIdleConns(n) |
プール内の最大アイドル接続数。 既定値: 2。 |
db.SetConnMaxLifetime(d) |
接続が再利用できる最大総時間。 デフォルト: 0 (制限なし)。 |
db.SetConnMaxIdleTime(d) |
接続が閉じるまでにアイドル状態にできる最大時間。 デフォルト: 0 (制限なし)。 |
Example
データベースを開いた直後にプールを設定してください:
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 に上限と下限を設定し、デプロイ構成によって古い接続が残ったり、接続の分散が偏ったりする可能性がある場合にのみ、接続の有効期間とアイドル時間の制限を追加します。
推奨設定
| シナリオ | マックスオープン | MaxIdle | マックスライフタイム | MaxIdleTime |
|---|---|---|---|---|
| 安定したSQL Serverネットワークパス上のWebアプリケーション | 25 | 10 | 0 | 0 |
| Azure SQL、ゲートウェイ、またはロードバランサーを通じたウェブアプリケーション | 25 | 10 | 5 分 | 1 分 |
| 高スループットサービス | 50-100 | 25 | 5 分 | 30 秒 |
| バックグラウンドジョブ/CLIツール | 5 | 2 | 0 | 0 |
| Azure SQL Database (Basic/Standard) | 10-20 | 5 | 5 分 | 1 分 |
Tip
MaxOpenConnsSQL 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)
キー フィールド:
| フィールド | 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に達したときに発生します。 新規発信者は接続が戻るまでブロックします。 症状には、レイテンシの増大、ゴールーチンの増加、および最終的にコンテキストの期限切れエラーが含まれます。
疲労監視
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のままです。 |
defer rows.Close()の直後にすぐに使QueryContext。 |
| 長時間トランザクション |
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 | レコメンデーション |
|---|---|
MaxOpenConns |
常に有界値に設定してください。 サーバーの接続制限より下のワークロードの並行処理に合わせて調整してください。 |
MaxIdleConns |
少なくとも MaxOpenConnsの半分に設定してください。 アイドル接続が少なすぎると、頻繁な再接続のオーバーヘッドが生じます。 |
ConnMaxLifetime |
Azure SQLや負荷分散環境の場合は5分に設定してください。 古い接続の蓄積を防ぎます。 |
ConnMaxIdleTime |
不要な接続を閉じるまで30〜60秒に設定してください。 |
| Monitoring |
db.Stats() の増加をポーリングし、WaitCount に対してアラートを出します。 |
| リソースのクリーンアップ | いつも defer rows.Close()、 defer tx.Rollback()、 defer conn.Close()。 |
| スタートアップ検証 |
db.PingContext後にsql.Openに連絡して接続を確認してください。 |