Sdružování připojení s go-mssqldb

Ovladač go-mssqldb využívá vestavěný pool připojení poskytovaný database/sql balíčkem Go. Každá sql.DB instance udržuje zásobník nečinných připojení, která jsou automaticky znovu použita. Tento článek vysvětluje, jak nastavit pool pro vaši pracovní zátěž.

Jak bazén funguje

Když voláte db.QueryContext, db.ExecContext, nebo jakoukoli jinou databázovou metodu:

  1. Bazén se snaží najít nečinné spojení.
  2. Pokud není k dispozici žádné nečinné připojení a pool nedosáhl maximální velikosti, vytvoří se nové připojení.
  3. Pokud je pool na maximální kapacitě, volání blokuje, dokud není k dispozici připojení.
  4. Po dokončení operace je spojení vráceno do poolu.

Metody konfigurace poolu

Nakonfigurujte pool pomocí metod na *sql.DB:

Metoda Description
db.SetMaxOpenConns(n) Maximální počet otevřených připojení (používaných + nečinných). Výchozí: 0 (neomezené).
db.SetMaxIdleConns(n) Maximální počet nečinných připojení v poolu. Výchozí hodnota: 2.
db.SetConnMaxLifetime(d) Maximální celková doba, po kterou lze spojení znovu použít. Výchozí: 0 (bez limitu).
db.SetConnMaxIdleTime(d) Maximální dobu, po kterou může spojení zůstat nečinné, než se uzavře. Výchozí: 0 (bez limitu).

Example

Nakonfigurujte pool ihned po otevření databáze:

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)

Tyto hodnoty berte jako výchozí bod, ne jako univerzální výchozí hodnotu. U mnoha služeb je jako první užitečný krok nastavit limity pro MaxOpenConns a MaxIdleConns a limity životnosti a doby nečinnosti přidat pouze tehdy, když váš způsob nasazení může vést k zastaralým nebo nerovnoměrně rozloženým připojením.

Scénář MaxOpen MaxIdle MaxLifetime MaxIdleTime
Webová aplikace ve stabilní síti SQL Serveru 25 10 0 0
Webová aplikace prostřednictvím Azure SQL, brány nebo load balanceru 25 10 5 minut 1 minuta
Služba s vysokou propustností 50-100 25 5 minut 30 sekund
Úloha na pozadí / nástroj CLI 5 2 0 0
Azure SQL Database (Basic/Standard) 10–20 5 5 minut 1 minuta

Tip

Nastavte MaxOpenConns pod limit připojení vaší instance SQL Server nebo úrovně Azure SQL. Překročení maximálního souběžného připojení serveru způsobuje neúspěchy přihlášení u všech klientů.

Krátké hodnoty ConnMaxLifetime a ConnMaxIdleTime snižují pravděpodobnost neplatných připojení po failoveru nebo recyklaci brány, ale také zvyšují četnost vytváření a ukončování připojení. Pokud se vaše aplikace připojuje přímo ke stabilní instanci SQL Serveru a nedochází k selháním způsobeným neplatnými připojeními, je rozumné ponechat obě hodnoty na 0.

Sledujte statistiky poolu

Použijte db.Stats() pro čtení aktuálních statistik poolu:

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)

Klíčová pole:

Obor Description
OpenConnections Úplné otevřené připojení (v provozu + nečinnost).
InUse Spojení jsou aktuálně ověřena volajícími.
Idle Spojení čekají v bazénu.
WaitCount Celkový počet případů, kdy musel volající čekat na spojení.
WaitDuration Celková kumulativní doba čekání.

Pokud WaitCount roste stabilně, nezvyšujte MaxOpenConns to automaticky. Nejprve ověřte, že řádky, transakce a vyhrazená připojení jsou rychle uzavírány, a potvrďte, že server zvládne větší pool.

SessionInitSQL

Používám SessionInitSQL SQL příkaz při každém novém připojení, které vstupuje do poolu. Tato funkce je užitečná pro nastavení možností na úrovni relace:

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)

Připnutí spojů

Některé operace připnou spojení tak, aby se nevrátilo do poolu, dokud operace neskončí:

  • Transakce (db.BeginTx) – Připojení je vyhrazeno, dokud není zavoláno Commit() nebo Rollback().
  • Jednorázová spojení (db.Conn) - Spojení je připnuto, dokud conn.Close() není vyvoláno.
  • Otevřené řádky (db.QueryContext) - Spojení je připnuté, dokud rows.Close() není vyvoláno.

Vždy tyto zdroje rychle zavřete, abyste nepřišli o bazén.

Detekce a řešení vyčerpání bazénu

K vyčerpání fondu dochází, když jsou všechna připojení používána a fond dosáhl MaxOpenConns. Noví volající blokují, dokud není spojení vráceno. Příznaky zahrnují vysokou latenci, hromadění gorutiny a případné chyby kontextových termínů.

Monitor vyčerpání

Pravidelně dotazujte db.Stats() a upozorněte při zjištění kolize:

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)
            }
        }
    }
}

Běžné příčiny a jejich řešení

Příčina Symptom Solution
MaxOpenConns příliš nízké pro dané zatížení WaitCount roste stabilně. Zvýšit MaxOpenConns.
Řádky nejsou uzavřené v chybových cestách InUse roste, Idle zůstává na nule. Použijte defer rows.Close() ihned po QueryContext.
Dlouhotrvající transakce InUse zůstává na vysoké úrovni. Udržujte transakce krátké. Používejte časové limity v kontextu.
db.Conn Používáno zbytečně InUse Vyšší, než jsem čekal. Používejte db.Conn jen tehdy, když potřebujete stav zaměřený na relaci (dočasné tabulky).
MaxOpenConns Nenastaveno (neomezené) Stovky otevřených připojení při zátěži. Vždy nastaveno MaxOpenConns na omezenou hodnotu.

Zdravotní kontroly a detekce zastaralého spojení

Pool aktivně nevaliduje nečinná připojení. Připojení, které zůstalo nečinné, zatímco ho server recykloval, selže při dalším použití. Konfigurujte a otáčejte ConnMaxLifetimeConnMaxIdleTime spojení dříve, než se stanou zastaranými:

// 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

Když ConnMaxIdleTime proaktivně uzavírá nečinná připojení, logy SQL Server mohou ukazovat ztrátu spojení. Jde o očekávané chování, nejde o únik připojení. Pokud váš DBA hlásí neočekávaná uzavření připojení, ověřte, že nastavení ConnMaxIdleTime odpovídá očekáváním týmu ohledně monitorování.

Pokud se vaše aplikace připojuje přes load balancer nebo Azure SQL s geo-replikací, nastavte ConnMaxLifetime na 5 minut nebo méně. Toto nastavení zajišťuje, že spojení jsou po failoveru přerozdělena mezi repliky.

Validujte konektivitu při spuštění

Vždy volejte db.PingContext po otevření databáze, abyste potvrdili, že připojovací řetězec je správný a server dostupný:

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)
}

Metriky exportního poolu

Zpřístupněte statistiky poolu svému monitorovacímu systému pravidelným čtením db.Stats():

Příklad Promethea

Zaregistrujte metriky, které sledují statistiky poolu, a pravidelně je aktualizujte:

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
        }
    }
}

Kontrolní seznam konfigurace bazénu

Area Recommendation
MaxOpenConns Vždy nastaveno na omezenou hodnotu. Nastavte ji podle souběžnosti pracovní zátěže tak, aby zůstala pod limitem počtu připojení serveru.
MaxIdleConns Nastavte alespoň na polovinu hodnoty MaxOpenConns. Příliš málo nečinných připojení způsobuje častou režii spojenou s opětovným navazováním připojení.
ConnMaxLifetime Nastavte na 5 minut pro Azure SQL nebo prostředí s vyrovnáváním zátěže. Zabraňuje hromadění neaktivních spojení.
ConnMaxIdleTime Nastavte na 30-60 sekund na uzavření spojení, která už nejsou potřeba.
Monitoring Pravidelně kontrolujte db.Stats() a upozorněte na růst WaitCount.
Vyčištění prostředků Vždy defer rows.Close(), defer tx.Rollback(), a defer conn.Close().
Validace spuštění Po sql.Open spusťte db.PingContext a ověřte připojení.