Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Este artigo fornece orientações sobre como otimizar o desempenho das aplicações Go que utilizam o go-mssqldb driver com o SQL Server.
Comece pelas mudanças de maior impacto
A maioria das aplicações não precisa de ajustes específicos do controlador no primeiro dia. Comece com estes passos antes de alterar o tamanho do pacote, adicionar declarações preparadas por todo o lado ou ajustar as opções em bloco:
- Defina um tamanho limitado do pool de ligação que corresponda aos limites do seu servidor.
- Adicione timeouts de contexto para que chamadas SQL bloqueadas não mantenham ligações ocupadas e façam a aplicação parecer congelada quando está sob carga.
- Corrija consultas lentas, índices em falta e idas e volta desnecessárias no SQL Server.
- Faz um benchmark da carga de trabalho antes e depois de cada alteração.
Para muitos serviços, as definições do agrupamento de ligações e a estrutura da consulta importam mais do que o tamanho do pacote ou a preparação de instruções.
Configuração do agrupamento de ligações
O database/sql agrupamento de ligações é o fator com maior impacto no desempenho. Pools insuficientemente dimensionados fazem com que as goroutines fiquem bloqueadas à espera de conexões, enquanto pools sobredimensionados desperdiçam recursos do servidor.
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
Monitorize db.Stats().WaitCount e db.Stats().WaitDuration para detetar contenção no conjunto. Para mais detalhes, veja Agrupamento de ligações.
Aumentar o tamanho do pacote
O tamanho padrão do pacote TDS é de 4.096 bytes. Para cargas de trabalho que transferem grandes conjuntos de resultados ou dados em massa, aumentar o tamanho do pacote reduz o número de viagens de ida e volta na rede.
sqlserver://<user>:<password>@<server>?database=AdventureWorks2025&packet+size=16384
Intervalo válido: 512 a 32.767. Valores de 8.192 ou 16.384 são comuns em cenários de alto rendimento.
Mantém o tamanho padrão do pacote, a menos que os dados de benchmark mostrem que a transferência de rede domina a carga de trabalho. Para tráfego ao estilo OLTP, com consultas e linhas pequenas, pacotes maiores frequentemente acrescentam complexidade sem um ganho significativo.
Utilizar declarações preparadas
Instruções preparadas evitam análises repetidas de consultas e compilação de planos no servidor. Use db.PrepareContext quando executa a mesma consulta muitas vezes com parâmetros diferentes:
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
Feche as instruções preparadas usando defer stmt.Close() para evitar consumir os handles de instruções preparadas do lado do servidor.
Não prepares todas as declarações por defeito. Ajudam mais quando a mesma afirmação se repete repetidamente num caminho quente. Para perguntas pontuais, QueryContext ou ExecContext geralmente é mais direto e rápido o suficiente.
Use cópias em massa para inserções grandes
As instruções INSERT individuais são lentas para grandes volumes de dados. A cópia em massa transmite os dados diretamente para o servidor, contornando o processador de consultas:
stmt, err := txn.Prepare(mssql.CopyIn("MyTable", mssql.BulkOptions{
Tablock: true,
RowsPerBatch: 5000,
}, "Col1", "Col2"))
Para mais detalhes, ver Operações em bloco.
Use varchar quando apropriado
Por predefinição, o código envia os parâmetros string como nvarchar (Unicode). Se as suas colunas usarem varchar, o servidor pode realizar conversão implícita e saltar índices. Use mssql.VarChar para enviar varchar parâmetros.
db.QueryContext(ctx, "SELECT * FROM Production.Product WHERE ProductNumber = @p1",
mssql.VarChar("FR-R92B-58"))
Utilize tempos limite de contexto
Defina prazos contextuais para consultas individuais para evitar que chamadas SQL bloqueadas fixem as ligações e atrasem os chamadores.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
rows, err := db.QueryContext(ctx, "SELECT * FROM LargeTable")
Encerre os recursos rapidamente
Abre *sql.Rows, *sql.Tx, e *sql.Conn os objetos fixam uma ligação a partir do pool. Feche-os sempre o mais rápido possível.
rows, err := db.QueryContext(ctx, query)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
// process
}
return rows.Err()
Reduzir viagens de ida e volta
-
Agrupe consultas numa única chamada sempre que possível:
"SELECT ...; SELECT ...;". -
Use cláusulas
OUTPUTem vez de chamadasSELECT SCOPE_IDENTITY()separadas. - Use parâmetros com valores de tabela para enviar múltiplas linhas numa única chamada em vez de fazer looping sobre inserções individuais.
Performance checklist (Lista de verificação do desempenho)
| Area | Recommendation |
|---|---|
| Piscina | Define MaxOpenConns com base nos limites do servidor e na concorrência da carga de trabalho. |
| Piscina | Definir MaxIdleConns para pelo menos metade de MaxOpenConns. |
| Network | Altere packet size apenas após realizar testes de desempenho de grandes transferências ou carregamentos em massa. |
| Queries | Use declarações preparadas apenas para declarações que se repetem frequentemente. |
| Queries | Utilize mssql.VarChar para colunas varchar para evitar conversões implícitas. |
| Cargas grandes | Use cópia em massa (mssql.CopyIn) para inserções em lote. |
| Resources | Feche os objetos Rows, Tx e Conn de imediato. |
| Timeouts | Defina prazos de contexto para todas as consultas e declarações. |
| Leitura intensa | Utilize ApplicationIntent=ReadOnly para réplicas de leitura. |
| Benchmarks | Use testing.B para medir antes e depois da otimização. |
| Monitoring | Ative o Query Store e utilize relatórios SSMS ou o Query Performance Insight. |
| Monitoring | Exportar db.Stats() para Prometheus ou OpenTelemetry. |
Operações de teste de desempenho da base de dados
Use Go's testing.B para medir o desempenho das operações da base de dados e validar alterações de otimização:
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)
}
}
}
Execute testes de desempenho com:
go test -bench=BenchmarkInsert -benchmem -count=5
Tip
Use -count=5 ou mais para obter resultados estatisticamente significativos. Usa o benchstat para comparar resultados de benchmarks antes e depois de uma alteração.
Usar encaminhamento apenas de leitura
Se o seu ambiente SQL Server tiver um grupo de disponibilidade com secundários legíveis, encaminhe as consultas só de leitura para a réplica secundária, definindo ApplicationIntent=ReadOnly na cadeia de ligação:
sqlserver://<user>:<password>@mylistener?database=AdventureWorks2025&ApplicationIntent=ReadOnly
Crie instâncias separadas *sql.DB para cargas de leitura e escrita:
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.
Análise do plano de consulta
Use SET SHOWPLAN_XML para recuperar o plano de execução de uma consulta sem o executar. Este método ajuda-o a identificar digitalizações de tabelas, índices em falta e operações dispendiosas:
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 afeta toda a conexão. Use sempre db.Conn(ctx) para isolar o modo SHOWPLAN numa ligação dedicada.
Utilize o Query Store para encontrar consultas lentas
Os benchmarks do Go e o timing do lado do cliente dizem-te quanto tempo demora uma consulta do ponto de vista da tua aplicação, mas esse número combina latência de rede, tempo de execução do servidor e processamento do cliente. A Query Store captura planos de execução e estatísticas de tempo de execução no servidor, para que possa ver exatamente como o SQL Server executou cada consulta, com que frequência foi executada e como o seu desempenho mudou ao longo do tempo.
O Query Store é especialmente útil para identificar deteção de parâmetros, regressões de plano e consultas que consomem mais recursos do servidor. Ative-o na tua base de dados se ainda não estiver ativado:
ALTER DATABASE [AdventureWorks2025] SET QUERY_STORE = ON;
Uma vez ativado, pode rever os dados de desempenho de várias formas:
- SQL Server Management Studio (SSMS): Expanda a sua base de dados no Object Explorer, abra a pasta Query Store e utilize os relatórios incorporados como Consultas que Consomem Recursos Maiores, Consultas Regressadas e Consumo Geral de Recursos.
- Portal Azure: Para Base de Dados SQL do Azure, abra a lâmina Query Performance Insight para ver as consultas que mais consomem recursos sem instalar qualquer ferramenta.
-
Transact-SQL (T-SQL): Consulte diretamente as vistas de catálogo
sys.query_store_runtime_statsesys.query_store_planda sua aplicação Go, se precisar de acesso programático.
Tip
A Query Store mantém dados ao longo dos reinícios dos servidores, para que possa analisar tendências de desempenho ao longo de dias ou semanas. Use o relatório de Consultas Regressadas para identificar rapidamente consultas que ficaram mais lentas após uma alteração de esquema ou código.
Utilizar o Painel de Desempenho
O Performance Dashboard é um relatório SSMS integrado que oferece uma visão geral em tempo real do estado do SQL Server. Clique com o botão direito do rato na instância do servidor no SSMS Object Explorer e selecione Relatórios>Relatórios padrão>Painel de desempenho.
O dashboard mostra:
- Esperas e gargalos atuais.
- Consultas recentes e caras.
- Tendências de uso de CPU, I/O e memória.
- Pedidos de utilizadores ativos e sessões bloqueadas.
O Performance Dashboard é útil durante o desenvolvimento e testes de carga para detetar rapidamente problemas sem escrever quaisquer consultas de diagnóstico.
Monitorizar métricas no servidor em Go
Se precisar de expor dados de desempenho do SQL Server a um sistema de monitorização como o Prometheus ou OpenTelemetry a partir da sua aplicação Go, consulte diretamente as vistas de gestão dinâmica (DMVs):
Consultas mais caras
Recupere as 10 principais consultas ordenadas pelo tempo médio decorrido:
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`)
Pedidos ativos atuais
Liste todos os pedidos atualmente em execução no servidor, excluindo a sua própria sessão:
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`)
Padrões eficientes em memória para grandes resultados
Linhas de fluxo sem acumulação
Processe as linhas uma de cada vez em vez de carregar todo o conjunto de resultados numa fatia:
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()
Processamento em lote com paginação por conjunto de chaves
Divida grandes varreduras de tabelas em blocos geríveis para limitar o uso de memória e evitar manter uma ligação durante longos períodos:
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
}