Transações, Prepared Statements & sql.Tx
Transações agrupam múltiplas instruções em uma unidade atômica.
Busque em todas as páginas da documentação
Transações agrupam múltiplas instruções em uma unidade atômica.
Prepared statements reutilizam SQL analisado para parâmetros repetidos.
sql.Tx une ambos: inicie com contexto, execute dentro do limite, então comite ou reverta.
BeginTx(ctx, opts) inicia uma transação com nível de isolamento opcional.
Commit persiste alterações; Rollback as descarta - chame um caminho após erros.
PrepareContext cria um Stmt; Tx.Stmt o reutiliza dentro de uma transação.
Use defer Rollback após BeginTx para que os caminhos de erro não vazem transações abertas.
Cartão de receita de referência rápida - pronto para copiar e colar.
func Transfer(ctx context.Context, db *sql.DB, from, to int64, amount int) error {
tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
if err != nil {
return err
}
defer tx.Rollback()
if _, err := tx.ExecContext(ctx,
`UPDATE accounts SET balance = balance - $1 WHERE id = $2`, amount, from,
); err != nil {
return err
}
if _, err := tx.ExecContext(ctx,
`UPDATE accounts SET balance = balance + $1 WHERE id = $2`, amount, to,
); err != nil {
return err
}
return tx.Commit()
}Quando usar isso:
package main
import (
"context"
"database/sql"
"fmt"
_ "github.com/mattn/go-sqlite3"
)
func main() {
db, _ := sql.Open("sqlite3", ":memory:")
defer db.Close()
ctx := context.Background()
_, _ = db.ExecContext(ctx, `CREATE TABLE ledger (
id INTEGER PRIMARY KEY,
balance INTEGER NOT NULL
)`)
_, _ = db.ExecContext(ctx, `INSERT INTO ledger (id, balance) VALUES (1, 100), (2, 0)`)
stmt, err := db.PrepareContext(ctx, `UPDATE ledger SET balance = balance + ? WHERE id = ?`)
if err != nil {
panic(err)
}
defer stmt.Close()
tx, err := db.BeginTx(ctx, nil)
if err != nil {
panic(err)
}
defer tx.Rollback()
txStmt := tx.Stmt(stmt)
if _, err := txStmt.ExecContext(ctx, -25, 1); err != nil {
panic(err)
}
if _, err := txStmt.ExecContext(ctx, 25, 2); err != nil {
panic(err)
}
fmt.Println(tx.Commit())
}O que isso demonstra:
PrepareContext uma vez, Tx.Stmt para vincular o stmt à transação ativa.defer tx.Rollback() cobre saídas por pânico e erro; Commit tem sucesso após o trabalho ser concluído.ctx flui através do início, execução e commit para cancelamento.database/sql.BeginTx retira uma conexão dedicada até o commit ou rollback.tx são executadas na mesma conexão, preservando as garantias de isolamento.Stmt podem cachear planos preparados no lado do servidor, dependendo das configurações do driver.Commit, o objeto de transação é inválido; inicie um novo BeginTx para mais trabalho.| Nível | Constante | Uso Típico |
|---|---|---|
| Padrão | nil opts | Padrão do driver (geralmente read committed) |
| Read committed | LevelReadCommitted | A maioria dos serviços OLTP |
| Repeatable read | LevelRepeatableRead | Leituras consistentes em uma tx |
| Serializable | LevelSerializable | O mais forte, pode tentar novamente em conflito |
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer func() {
_ = tx.Rollback() // no-op após Commit bem-sucedido
}()Rollback após Commit retorna sql.ErrTxDone - seguro para ignorar em defer.sql.ErrTxDone apenas se você cometer duas vezes por engano.Algumas equipes usam SQL SAVEPOINT bruto dentro de uma tx para rollback parcial.
Prefira transações menores e menos numerosas em vez de aninhamento profundo de savepoints, a menos que a equipe de banco de dados documente padrões.
Rollback no caminho de erro - A conexão permanece em estado abortado e vaza do pool. Correção: defer tx.Rollback() imediatamente após BeginTx.db.Exec dentro de uma tx aberta para escritas relacionadas - Declarações são executadas em conexões diferentes e quebram a atomicidade. Correção: use apenas tx.ExecContext para trabalho transacional.stmt.Exec enquanto uma tx está aberta ignora a conexão da transação. Correção: tx.Stmt(stmt) ou prepare diretamente na tx.| Alternativa | Use Quando | Não Use Quando |
|---|---|---|
Exec de instrução única | UPDATE atômico | Invariantes multi-tabela |
| Saga / padrão outbox | Consistência entre serviços | ACID de banco de dados único é suficiente |
| Helper de transação ORM | Equipe usando wrappers GORM/sqlx | Você precisa de documentação explícita de isolamento |
| Locks consultivos | Mutex em nível de aplicação no DB | Atualizações de linha simples são suficientes |
SELECT FOR UPDATE | Lock de linha pessimista | Alta contenção sem tx curta |
Sim, imediatamente após BeginTx.
Commit bem-sucedido torna Rollback adiado um no-op.
Exponha helpers WithTx(ctx, fn) ou aceite *sql.Tx em métodos internos.
Mantenha *sql.DB como a dependência padrão do construtor.
BeginTx e ExecContext honram o ctx; reverta no cancelamento para liberar a conexão.
Não deixe txs abertas após a saída do handler.
Quando o mesmo SQL é executado muitas vezes por segundo e o profiling mostra sobrecarga de análise.
Muitos drivers cacheiam automaticamente; faça benchmark primeiro.
Alguns bancos de dados otimizam txs somente leitura; use TxOptions{ReadOnly: true} quando o driver suportar para consultas de relatórios.
Force a segunda instrução a falhar e afirme que a primeira alteração não é visível após Rollback.
Use SQLite ou Postgres reais em testes de integração.
Compartilhe uma tx apenas quando o driver e o nível de isolamento permitirem uso concorrente - geralmente uma tx por goroutine é mais seguro.
Serialize escritas em uma única conexão de tx.
Alguns DDLs não podem ser executados dentro de txs em certos motores; ferramentas de migração documentam o comportamento por banco de dados.
Serviços online não devem executar migrações por solicitação.
sql.ErrTxDone - indica um bug lógico; corrija os caminhos de chamada em vez de engolir silenciosamente.
Uma tx aberta retém uma conexão do pool até o commit ou rollback.
Mantenha txs curtas para evitar a fome do pool sob carga.
Versões de Stack: Esta página foi escrita para Go 1.26.x (padrão GC Green Tea, go fix modernizers - verifique o patch na compilação), chi (latest - verifique na compilação), gin (latest - verifique na compilação), echo (latest - verifique na compilação), google.golang.org/grpc (latest - verifique na compilação), sigs.k8s.io/controller-runtime (latest - verifique na compilação), kubebuilder (latest - verifique na compilação), tinygo (latest - verifique os alvos de placa na compilação), wazero (latest - verifique na compilação), e golangci-lint (latest - verifique o conjunto de linters na compilação).
Revisado por Chris St. John·Última atualização: 18 de jul. de 2026