Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Tento článek poskytuje návod, jak optimalizovat výkon aplikací Go, které používají go-mssqldb ovladač se SQL Server.
Začněte s největšími změnami
Většina aplikací nepotřebuje už od prvního dne ladění specifické pro ovladač. Začněte těmito kroky, než změníte velikost paketu, přidáte všude připravené příkazy nebo upravíte nastavení hromadných operací:
- Nastavte omezenou velikost poolu připojení, která odpovídá limitům vašeho serveru.
- Přidejte časové limity kontextu, aby blokované SQL volání nepřipínaly připojení a aplikace se při zatížení nejevila jako zamrzlá.
- Opravte pomalé dotazy, chybějící indexy a zbytečné cesty tam a zpět v SQL Server.
- Porovnajte pracovní zátěž před a po každé změně.
U mnoha služeb jsou nastavení poolu spojení a tvar dotazu důležitější než velikost paketu nebo příprava příkazů.
Ladění spojovacího bazénu
Poolová database/sql spojovací kapacita je nejvlivnější výkonnostní páka. Nedostatečně provisionované pooly způsobují, že goroutine blokují čekání na připojení, zatímco předimenzované pooly plýtvají serverovými zdroji.
db.SetMaxOpenConns(25) // Match your workload concurrency
db.SetMaxIdleConns(10) // Keep warm connections ready
db.SetConnMaxLifetime(5 * time.Minute) // Recycle connections periodically
db.SetConnMaxIdleTime(1 * time.Minute) // Close stale idle connections
Sledujte db.Stats().WaitCount a db.Stats().WaitDuration, abyste odhalili kolize ve fondu. Podrobnosti najdete v části Sdružování připojení.
Zvětšení velikosti paketu
Výchozí velikost TDS paketu je 4 096 bajtů. U pracovních zátěží, které přenášejí velké výsledkové sady nebo hromadná data, zvětšení velikosti paketu snižuje počet síťových okružních cest.
sqlserver://<user>:<password>@<server>?database=AdventureWorks2025&packet+size=16384
Platný rozsah: 512 až 32 767. Hodnoty 8 192 nebo 16 384 jsou běžné pro scénáře s vysokou propustností.
Nechte výchozí velikost paketu, pokud benchmarková data neukážou, že síťový přenos dominuje zátěži. U provozu ve stylu OLTP s malými dotazy a řádky často větší pakety přidávají složitost bez významného zisku.
Používejte připravené výroky
Připravené příkazy se vyhýbají opakovanému parsování dotazů a plánují kompilaci na serveru. Použijte db.PrepareContext , když spouštěte stejný dotaz mnohokrát s různými parametry:
stmt, err := db.PrepareContext(ctx,
"SELECT TOP (1) FirstName + ' ' + LastName AS Name FROM Sales.vSalesPerson WHERE CountryRegionName = @p1")
if err != nil {
log.Fatal(err)
}
defer stmt.Close()
for _, loc := range locations {
var name string
stmt.QueryRowContext(ctx, loc).Scan(&name)
}
Tip
Uzavřete připravené příkazy pomocí defer stmt.Close(), abyste zabránili spotřebovávání deskriptorů připravených příkazů na straně serveru.
Nepřipravujte každý výrok automaticky. Nejvíce pomáhají, když se stejný výrok opakovaně opakuje v horké dráze. U jednorázových dotazů QueryContextExecContext je to obvykle přímočařejší a rychlejší.
Používejte hromadné texty pro velké přílohy
Jednotlivé příkazy INSERT jsou pomalé pro velké datové zatížení. Hromadné kopírovaní přenáší data přímo na server, přičemž obchází procesor dotazů:
stmt, err := txn.Prepare(mssql.CopyIn("MyTable", mssql.BulkOptions{
Tablock: true,
RowsPerBatch: 5000,
}, "Col1", "Col2"))
Podrobnosti viz Hromadné operace.
Varchar používejte podle potřeby
Ve výchozím nastavení kód odesílá string parametry jako nvarchar (Unicode). Pokud vaše sloupce používají varchar, server může provádět implicitní konverzi a přeskočit indexy. Používá se mssql.VarChar k odesílání varchar parametrů.
db.QueryContext(ctx, "SELECT * FROM Production.Product WHERE ProductNumber = @p1",
mssql.VarChar("FR-R92B-58"))
Používejte časové limity pro kontext
Nastavte kontextové termíny pro jednotlivé dotazy, abyste zabránili blokovaným SQL hovorům, které by připínaly spojení a zdržovaly volající.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
rows, err := db.QueryContext(ctx, "SELECT * FROM LargeTable")
Uzavírejte zdroje co nejdříve
Otevřete *sql.Rows, *sql.Tx, a objekty *sql.Conn připnou spojení z poolu. Vždy je zavři co nejdříve.
rows, err := db.QueryContext(ctx, query)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
// process
}
return rows.Err()
Snižte zpáteční jízdy tam a zpět
-
Seskupujte dotazy do jednoho volání, pokud je to možné:
"SELECT ...; SELECT ...;". -
Používejte
OUTPUTklauzule místo samostatnýchSELECT SCOPE_IDENTITY()volání. - Použijte parametry s tabulkovými hodnotami k odeslání více řádků v jednom volání místo opakování jednotlivých vkladů.
Kontrolní seznam výkonu
| Area | Recommendation |
|---|---|
| Bazén | Nastavte MaxOpenConns na základě limitů serveru a souběžnosti s pracovní zátěží. |
| Bazén | Nastavte MaxIdleConns alespoň na polovinu hodnoty MaxOpenConns. |
| Network | Změňte packet size až po otestování výkonu při velkých přenosech nebo hromadném načítání dat. |
| Queries | Používejte připravené výroky pouze pro věty, které se často opakují. |
| Queries | Použijte mssql.VarChar pro sloupce varchar, aby se předešlo implicitním převodům. |
| Velké zatížení | Pro dávkové vložení použijte hromadnou kopii (mssql.CopyIn). |
| Resources | Uzavřete Rows, Tx, a Conn objekty okamžitě. |
| Časové limity | Nastavte kontextové termíny pro všechny dotazy a výkazy. |
| S převahou čtení | Použití ApplicationIntent=ReadOnly pro čtení replik. |
| Benchmarks | Používejte testing.B k měření před a po optimalizaci. |
| Monitoring | Povolte Query Store a používejte SSMS reporty nebo Query Performance Insight. |
| Monitoring | Exportovat db.Stats() do Prometheus nebo OpenTelemetry. |
Operace benchmarkové databáze
Použijte Go's testing.B k měření výkonu databázových operací a ověření optimalizačních změn:
func BenchmarkInsertSingle(b *testing.B) {
db := setupDB(b)
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := db.ExecContext(ctx,
"INSERT INTO BenchTable (Name) VALUES (@p1)",
sql.Named("p1", fmt.Sprintf("bench-%d", i)))
if err != nil {
b.Fatal(err)
}
}
}
func BenchmarkInsertBulk(b *testing.B) {
db := setupDB(b)
ctx := context.Background()
b.ResetTimer()
for i := 0; i < b.N; i++ {
txn, err := db.BeginTx(ctx, nil)
if err != nil {
b.Fatal(err)
}
stmt, err := txn.Prepare(mssql.CopyIn("BenchTable",
mssql.BulkOptions{}, "Name"))
if err != nil {
b.Fatal(err)
}
for j := 0; j < 1000; j++ {
if _, err := stmt.Exec(fmt.Sprintf("bench-%d-%d", i, j)); err != nil {
b.Fatal(err)
}
}
if _, err := stmt.Exec(); err != nil {
b.Fatal(err)
}
stmt.Close()
if err := txn.Commit(); err != nil {
b.Fatal(err)
}
}
}
Spusť benchmarky s:
go test -bench=BenchmarkInsert -benchmem -count=5
Tip
Použijte -count=5 nebo vyšší pro získání statisticky smysluplných výsledků. Použijte benchstat k porovnání výsledků benchmarků před a po změně.
Použijte směrování pouze pro čtení
Pokud má vaše prostředí SQL Server skupinu dostupnosti s čitelnými sekundárními dotazy, směrujte dotazy pouze pro čtení do sekundární repliky nastavením ApplicationIntent=ReadOnly v připojovací řetězec:
sqlserver://<user>:<password>@mylistener?database=AdventureWorks2025&ApplicationIntent=ReadOnly
Vytvořte samostatné *sql.DB instance pro čtení a zápis:
writeDB, err := sql.Open("sqlserver",
"sqlserver://<user>:<password>@mylistener?database=AdventureWorks2025")
if err != nil {
log.Fatal(err)
}
readDB, err := sql.Open("sqlserver",
"sqlserver://<user>:<password>@mylistener?database=AdventureWorks2025&ApplicationIntent=ReadOnly")
if err != nil {
log.Fatal(err)
}
// Use readDB for reports, dashboards, and analytics.
// Use writeDB for inserts, updates, and deletes.
Analýza plánu dotazu
Použijte SET SHOWPLAN_XML k získání plánu provedení dotazu bez jeho spuštění. Tato metoda vám pomůže identifikovat skeny tabulek, chybějící indexy a nákladné operace:
func getQueryPlan(ctx context.Context, db *sql.DB, query string) (string, error) {
// Use a dedicated connection so SHOWPLAN mode doesn't affect other queries.
conn, err := db.Conn(ctx)
if err != nil {
return "", err
}
defer conn.Close()
// Enable SHOWPLAN_XML mode.
_, err = conn.ExecContext(ctx, "SET SHOWPLAN_XML ON")
if err != nil {
return "", err
}
var planXML string
err = conn.QueryRowContext(ctx, query).Scan(&planXML)
if err != nil {
return "", err
}
// Disable SHOWPLAN_XML mode.
_, _ = conn.ExecContext(ctx, "SET SHOWPLAN_XML OFF")
return planXML, nil
}
Warning
SET SHOWPLAN_XML ON Ovlivňuje celé spojení. Vždy používejte db.Conn(ctx) k izolaci režimu SHOWPLAN na vyhrazené připojení.
Použijte Query Store k nalezení pomalých dotazů
Go benchmarky a časování na straně klienta vám řeknou, jak dlouho trvá dotaz z pohledu vaší aplikace, ale toto číslo kombinuje latenci sítě, dobu spuštění serveru a zpracování klienta. Query Store zaznamenává plány provádění a statistiky běhu na serveru, takže můžete přesně vidět, jak SQL Server vykonával každý dotaz, jak často běžel a jak se jeho výkon v průběhu času měnil.
Query Store je zvláště užitečný pro identifikaci sniffingu parametrů, regrese plánů a dotazů, které spotřebovávají nejvíce serverových zdrojů. Povolte to ve své databázi, pokud to ještě není povolené:
ALTER DATABASE [AdventureWorks2025] SET QUERY_STORE = ON;
Jakmile je aktivován, můžete si data o výkonu prohlížet několika způsoby:
- SQL Server Management Studio (SSMS): Rozšiřte svou databázi v Průzkumník objektů, otevřete složku Query Store a použijte vestavěné reporty jako Top Resource Consuming Queries, Regressed Queries a Overall Resource Consumption.
- Azure portál: Pro Azure SQL Database otevřete blade Query Performance Insight, abyste viděli nejvíce náročné dotazy bez instalace jakýchkoli nástrojů.
-
Transact-SQL (T-SQL): Pokud potřebujete programový přístup, dotazujte se přímo z aplikace Go na zobrazení katalogu
sys.query_store_runtime_statsasys.query_store_plan.
Tip
Query Store uchovává data při restartu serveru, takže můžete analyzovat trendy výkonu během dnů nebo týdnů. Použijte report Regressed Queries k rychlému odhalení dotazů, které se zpomalily po změně schématu nebo kódu.
Použijte panel výkonu
Performance Dashboard je vestavěná SSMS zpráva, která poskytuje aktuální přehled o stavu SQL Server v reálném čase. Klikněte pravým tlačítkem na instanci serveru v SSMS Průzkumník objektů a vyberte Reports> StandardReports>Performance Dashboard.
Řídicí panel zobrazuje:
- Současné čekání a úzká místa.
- Nedávné drahé dotazy.
- Trendy v používání CPU, I/O a paměti.
- Aktivní uživatelské požadavky a blokované relace.
Výkonnostní dashboard je užitečný při vývoji a zátěžovém testování pro rychlé odhalení problémů bez psaní diagnostických dotazů.
Monitorování serverových metrik z Go
Pokud potřebujete z vaší aplikace v Go zpřístupnit systému monitorování, jako je Prometheus nebo OpenTelemetry, data o výkonu SQL Serveru, dotazujte se přímo na dynamické pohledy pro správu (DMV):
Nejdražší dotazy
Získejte 10 nejlepších dotazů seřazených podle průměrného uplynulého času:
rows, err := db.QueryContext(ctx, `
SELECT TOP 10
qs.total_elapsed_time / qs.execution_count AS avg_elapsed_us,
qs.execution_count,
qs.total_logical_reads / qs.execution_count AS avg_reads,
SUBSTRING(st.text, (qs.statement_start_offset/2)+1,
((CASE qs.statement_end_offset
WHEN -1 THEN DATALENGTH(st.text)
ELSE qs.statement_end_offset
END - qs.statement_start_offset)/2)+1) AS query_text
FROM sys.dm_exec_query_stats AS qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st
ORDER BY avg_elapsed_us DESC`)
Aktuální aktivní požadavky
Vypište všechny požadavky právě zpracovávané na serveru, s výjimkou vaší vlastní relace:
rows, err := db.QueryContext(ctx, `
SELECT
r.session_id,
r.status,
r.wait_type,
r.cpu_time,
r.logical_reads,
t.text AS query_text
FROM sys.dm_exec_requests AS r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS t
WHERE r.session_id != @@SPID`)
Paměťově efektivní vzory pro velké výsledky
Proudové řady bez akumulace
Zpracovávejte řádky po jednom namísto načtení celé sady výsledků do pole:
rows, err := db.QueryContext(ctx, "SELECT Id, Data FROM BigTable")
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var id int
var data string
if err := rows.Scan(&id, &data); err != nil {
return err
}
// Process immediately, don't append to a slice.
process(id, data)
}
return rows.Err()
Dávkové zpracování s paginací klíčů
Rozdělte velké tabulkové skenování na zvládnutelné části, abyste omezili využití paměti a vyhnuli se dlouhodobému držení spojení:
func processBatched(ctx context.Context, db *sql.DB, batchSize int) error {
var lastID int
for {
rows, err := db.QueryContext(ctx, `
SELECT TOP(@batch) Id, Data FROM BigTable
WHERE Id > @lastID ORDER BY Id`,
sql.Named("batch", batchSize),
sql.Named("lastID", lastID))
if err != nil {
return err
}
var count int
for rows.Next() {
var id int
var data string
if err := rows.Scan(&id, &data); err != nil {
return err
}
process(id, data)
lastID = id
count++
}
if err := rows.Err(); err != nil {
return err
}
rows.Close()
if count < batchSize {
break // No more rows.
}
}
return nil
}