Kommentar
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Den här artikeln ger vägledning för hur man optimerar prestandan hos Go-applikationer som använder drivrutinen go-mssqldb med SQL Server.
Börja med de förändringar med störst påverkan
De flesta applikationer behöver inte drivrutinsspecifik justering redan från dag ett. Börja med dessa steg innan du ändrar paketstorlek, lägger till förberedda satser överallt eller justerar bulkalternativ:
- Sätt en begränsad anslutningspoolstorlek som matchar dina servergränser.
- Lägg till kontexttidsavbrott så att blockerade SQL-anrop inte låser anslutningar och gör att appen ser frusen ut under belastning.
- Åtgärda långsamma frågor, saknade index och onödiga rundturer i SQL Server.
- Benchmarka arbetsbelastningen före och efter varje ändring.
För många tjänster spelar anslutningspoolinställningar och frågeform större roll än paketstorlek eller formulering av satser.
Justering av anslutningspool
database/sql anslutningspoolen är den faktor som påverkar prestandan mest. Underprovisionerade pooler gör att goroutines blockerar väntan på anslutningar, medan överprovisionerade pooler slösar serverresurser.
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
Övervaka db.Stats().WaitCount och db.Stats().WaitDuration för att upptäcka konkurrens i poolen. För mer information, se Connection pooling.
Öka paketstorleken
Standard TDS-paketstorlek är 4 096 byte. För arbetsbelastningar som överför stora resultatuppsättningar eller bulkdata minskar en ökning av paketstorleken antalet nätverksrundturer.
sqlserver://<user>:<password>@<server>?database=AdventureWorks2025&packet+size=16384
Giltigt intervall: 512 till 32 767. Värden på 8 192 eller 16 384 är vanliga för högkapacitetsscenarier.
Behåll standardpaketstorleken om inte benchmarkdata visar att nätverksöverföring dominerar arbetsbelastningen. För OLTP-liknande trafik med små frågor och rader tillför större paket ofta komplexitet utan någon meningsfull vinst.
Använd förberedda uttalanden
Förberedda satser undviker upprepad frågeparsning och planerar kompilering på servern. Använd db.PrepareContext när du kör samma fråga många gånger med olika parametrar:
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
Stäng förberedda instruktioner med defer stmt.Close() för att undvika att förbruka handtag för förberedda instruktioner på serversidan.
Förbered inte varje uttalande som standard. De hjälper mest när samma påstående upprepas i en het väg. För enstaka frågor är QueryContext eller ExecContext oftast enklare och snabbt nog.
Använd masskopiering för stora infogningar
Enskilda INSERT uttalanden är långsamma för stora datamängder. Masskopiering strömmar data direkt till servern och kringgår frågeprocessorn:
stmt, err := txn.Prepare(mssql.CopyIn("MyTable", mssql.BulkOptions{
Tablock: true,
RowsPerBatch: 5000,
}, "Col1", "Col2"))
Mer information finns i Massåtgärder.
Använd varchar när det är lämpligt
Som standard skickar string koden parametrar som nvarchar (Unicode). Om dina kolumner använder varchar, kan servern utföra implicit konvertering och hoppa över index. Använd mssql.VarChar för att skicka varchar parametrar.
db.QueryContext(ctx, "SELECT * FROM Production.Product WHERE ProductNumber = @p1",
mssql.VarChar("FR-R92B-58"))
Använd tidsgränser för kontext
Ange tidsgränser för kontexten för enskilda frågor för att förhindra att blockerade SQL-anrop binder upp anslutningar och fördröjer anropande processer.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
rows, err := db.QueryContext(ctx, "SELECT * FROM LargeTable")
Stäng resurser snabbt
Objekt av typen *sql.Rows, *sql.Tx och *sql.Conn som är öppna binder upp en anslutning från poolen. Stäng dem alltid så snart som möjligt.
rows, err := db.QueryContext(ctx, query)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
// process
}
return rows.Err()
Minska tur- och returresor
-
Batcha förfrågningar i ett enda anrop när det är möjligt:
"SELECT ...; SELECT ...;" -
Använd
OUTPUTsatser istället för separataSELECT SCOPE_IDENTITY()anrop. - Använd tabellvärda parametrar för att skicka flera rader i ett enda samtal istället för att loopa över enskilda inserts.
Checklista för prestanda
| Area | Recommendation |
|---|---|
| Bassäng | Ställ in MaxOpenConns utifrån serverns begränsningar och hur många arbetsbelastningar som körs samtidigt. |
| Bassäng | Ställ MaxIdleConns in på minst hälften av MaxOpenConns. |
| Network | Byt packet size först efter att ha prestandatestat stora överföringar eller massinläsningar. |
| Queries | Använd förberedda uttalanden endast för uttalanden som upprepas ofta. |
| Queries | Använd mssql.VarChar för varchar kolumner för att undvika implicita konverteringar. |
| Stora laster | Använd bulkkopiering (mssql.CopyIn) för batchinsatser. |
| Resources | Stäng Rows, Tx, och Conn objekt omedelbart. |
| Timeouts | Sätt kontextdeadlines för alla frågor och uttalanden. |
| Lästung krävande | Använd ApplicationIntent=ReadOnly för läsrepliker. |
| Prestandajämförelser | Använd testing.B för att mäta före och efter optimering. |
| Monitoring | Aktivera Query Store och använd SSMS-rapporter eller Query Performance Insight. |
| Monitoring | Exportera db.Stats() till Prometheus eller OpenTelemetry. |
Prestandatest av databasoperationer
Använd Gos testing.B för att mäta prestandan i databasoperationer och validera optimeringsändringar:
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)
}
}
}
Kör benchmarks med:
go test -bench=BenchmarkInsert -benchmem -count=5
Tip
Använd -count=5 eller högre för att få statistiskt meningsfulla resultat. Använd benchstat för att jämföra benchmarkresultat före och efter en förändring.
Använd skrivskyddad routing
Om din SQL Server-miljö har en tillgänglighetsgrupp med läsbara sekundärfiler, dirivar read-only-frågor till den sekundära repliken genom att ange ApplicationIntent=ReadOnly i reťazec pripojenia:
sqlserver://<user>:<password>@mylistener?database=AdventureWorks2025&ApplicationIntent=ReadOnly
Skapa separata *sql.DB instanser för läs- och skrivarbetsbelastningar:
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.
Analys av frågeplan
Använd SET SHOWPLAN_XML för att hämta exekveringsplanen för en fråga utan att köra den. Denna metod hjälper dig att identifiera tabellskanningar, saknade index och dyra operationer:
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
}
Varning
SET SHOWPLAN_XML ON påverkar hela anslutningen. Använd alltid db.Conn(ctx) för att isolera SHOWPLAN-läget till en dedikerad anslutning.
Använd Query Store för att hitta långsamma frågor
Go-benchmarks och klienttidtagning visar hur lång tid en fråga tar ur applikationens perspektiv, men det antalet kombinerar nätverkslatens, serverexekveringstid och klientbearbetning. Query Store fångar exekveringsplaner och körningsstatistik på servern, så du kan se exakt hur SQL Server körde varje fråga, hur ofta den kördes och hur dess prestanda förändrades över tid.
Query Store är särskilt användbart för att identifiera parametersniffing, planregressioner och frågor som förbrukar mest serverresurser. Aktivera det i din databas om det inte redan är aktiverat:
ALTER DATABASE [AdventureWorks2025] SET QUERY_STORE = ON;
När det är aktiverat kan du granska prestandadata på flera sätt:
- SQL Server Management Studio (SSMS): Expandera din databas i Object Explorer, öppna mappen Query Store och använd inbyggda rapporter som Top Resource Consuming Queries, Regressed Queries och Overall Resource Consumption.
- Azure-portalen: För Azure SQL Database, öppna bladet Query Performance Insight för att se de mest resurskrävande frågorna utan att installera några verktyg.
-
Transact-SQL (T-SQL): Fråga
sys.query_store_runtime_stats- ochsys.query_store_plan-katalogvyerna direkt från din Go-applikation om du behöver programmatisk åtkomst.
Tip
Query Store lagrar data över serveromstarter, så du kan analysera prestandatrender över dagar eller veckor. Använd rapporten Regressed Queries för att snabbt upptäcka frågor som blev långsammare efter en schema- eller kodändring.
Använd prestandainstrumentpanelen
Performance Dashboard är en inbyggd SSMS-rapport som ger en realtidsöversikt över SQL Server:s hälsa. Högerklicka på serverinstansen i SSMS Object Explorer och välj Reports>Standard Reports>Performance Dashboard.
Instrumentpanelen visar:
- Nuvarande väntetider och flaskhalsar.
- Senaste kostsamma frågorna
- CPU-, I/O- och minnesanvändningstrender.
- Aktiva användarförfrågningar och blockerade sessioner.
Prestandainstrumentpanelen är användbar under utveckling och belastningstestning för att snabbt upptäcka problem utan att behöva skriva några diagnostiska frågor.
Övervaka server-side-metrik från Go
Om du behöver göra prestandadata från SQL Server tillgängliga för ett övervakningssystem som Prometheus eller OpenTelemetry från din Go-applikation, fråga dynamiska hanteringsvyer (DMV:er) direkt:
De dyraste frågorna
Hämta de 10 vanligaste frågorna rankade efter genomsnittlig förfluten tid:
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`)
Nuvarande aktiva förfrågningar
Lista alla för närvarande körande förfrågningar på servern, exklusive din egen session:
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`)
Minneseffektiva mönster för stora resultat
Strömma rader utan att ackumulera
Bearbeta rader en i taget i stället för att läsa in hela resultatuppsättningen i en slice:
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()
Batchbearbetning med tangentsättspaginering
Dela upp stora tabellsskanningar i hanterbara delar för att begränsa minnesanvändningen och undvika att hålla en anslutning under längre perioder:
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
}