Описание типа Tx database/sql
Categories:
type Tx
type Tx struct {
// содержит отфильтрованные или неэкспортируемые поля
}
Tx — это незавершенная транзакция базы данных.
Транзакция должна заканчиваться вызовом Tx.Commit или Tx.Rollback.
После вызова Tx.Commit или Tx.Rollback все операции по транзакции завершаются с ошибкой ErrTxDone.
Инструкции, подготовленные для транзакции вызовом методов Tx.Prepare или Tx.Stmt транзакции, закрываются вызовом Tx.Commit или Tx.Rollback.
func (*Tx) Commit
func (tx *Tx) Commit() error
Commit фиксирует транзакцию.
func (*Tx) Exec
func (tx *Tx) Exec(query string, args ...any) (Result, error)
Exec выполняет запрос, который не возвращает строки. Например: INSERT и UPDATE.
Exec использует context.Background внутренне; для указания контекста используйте Tx.ExecContext.
func (*Tx) ExecContext
func (tx *Tx) ExecContext(ctx context.Context, query string, args ...any) (Result, error)
ExecContext выполняет запрос, который не возвращает строки. Например: INSERT и UPDATE.
Пример
package main
import (
"context"
"database/sql"
"log"
_ "github.com/go-sql-driver/mysql" // Импорт драйвера БД
)
func main() {
// Инициализация подключения к БД
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
defer db.Close()
// Проверка соединения
ctx := context.Background()
if err := db.PingContext(ctx); err != nil {
log.Fatal("Database connection failed:", err)
}
// Начало транзакции
tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
if err != nil {
log.Fatal("Failed to begin transaction:", err)
}
// Выполнение операций в транзакции
id := 37
_, execErr := tx.ExecContext(ctx, "UPDATE users SET status = ? WHERE id = ?", "paid", id)
if execErr != nil {
// Попытка отката при ошибке
if rollbackErr := tx.Rollback(); rollbackErr != nil {
log.Printf("UPDATE failed: %v, rollback also failed: %v", execErr, rollbackErr)
} else {
log.Printf("UPDATE failed, transaction rolled back: %v", execErr)
}
return
}
// Фиксация транзакции
if err := tx.Commit(); err != nil {
log.Fatal("Failed to commit transaction:", err)
}
log.Println("Transaction completed successfully")
}
func (*Tx) Prepare
func (tx *Tx) Prepare(query string) (*Stmt, error)
Prepare создает подготовленное выражение для использования в транзакции.
Возвращаемое выражение работает в рамках транзакции и будет закрыто после фиксации или отката транзакции.
Чтобы использовать существующее подготовленное выражение в этой транзакции, см. Tx.Stmt.
Prepare использует context.Background внутренне; чтобы указать контекст, используйте Tx.PrepareContext.
Пример
package main
import (
"context"
"database/sql"
"log"
_ "github.com/go-sql-driver/mysql" // Импорт драйвера БД
)
func main() {
// Инициализация подключения к БД
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname?parseTime=true")
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
defer db.Close()
// Проверка соединения
if err := db.Ping(); err != nil {
log.Fatal("Database connection failed:", err)
}
projects := []struct {
mascot string
release int
}{
{"tux", 1991},
{"duke", 1996},
{"gopher", 2009},
{"moby dock", 2013},
}
// Начало транзакции
ctx := context.Background()
tx, err := db.BeginTx(ctx, nil)
if err != nil {
log.Fatal("Failed to begin transaction:", err)
}
// Откат в случае ошибки (игнорируется если был Commit)
defer func() {
if err := tx.Rollback(); err != nil && err != sql.ErrTxDone {
log.Printf("Warning: rollback failed: %v", err)
}
}()
// Подготовка выражения
stmt, err := tx.PrepareContext(ctx, "INSERT INTO projects(id, mascot, release, category) VALUES(?, ?, ?, ?)")
if err != nil {
log.Fatal("Failed to prepare statement:", err)
}
defer stmt.Close()
// Вставка данных
for id, project := range projects {
if _, err := stmt.ExecContext(ctx, id+1, project.mascot, project.release, "open source"); err != nil {
log.Fatal("Failed to insert project:", err)
}
}
// Фиксация транзакции
if err := tx.Commit(); err != nil {
log.Fatal("Failed to commit transaction:", err)
}
log.Println("Successfully inserted", len(projects), "projects")
}
Дополнительные рекомендации:
-
Параметры подключения:
db.SetMaxOpenConns(25) db.SetMaxIdleConns(25) db.SetConnMaxLifetime(5*time.Minute)
-
Пакетная вставка: Для большого количества данных рассмотрите возможность:
// Начало пакетной вставки values := make([]interface{}, 0, len(projects)*4) query := "INSERT INTO projects(id, mascot, release, category) VALUES" for id, project := range projects { if id > 0 { query += "," } query += "(?, ?, ?, ?)" values = append(values, id+1, project.mascot, project.release, "open source") } if _, err := tx.ExecContext(ctx, query, values...); err != nil { log.Fatal(err) }
-
Таймауты:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel()
-
Валидация данных: Добавьте проверку данных перед вставкой:
if project.release < 1990 || project.release > time.Now().Year() { log.Printf("Invalid release year for %s: %d", project.mascot, project.release) continue }
func (*Tx) PrepareContext
func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error)
PrepareContext создает подготовленное выражение для использования в транзакции.
Возвращенное выражение работает в рамках транзакции и будет закрыто после фиксации или отката транзакции.
Чтобы использовать существующее подготовленное выражение в этой транзакции, см. Tx.Stmt.
Предоставленный контекст будет использоваться для подготовки контекста, а не для выполнения возвращаемого оператора. Возвращаемый оператор будет выполняться в контексте транзакции.
func (*Tx) Query
func (tx *Tx) Query(query string, args ...any) (*Rows, error)
Query выполняет запрос, который возвращает строки, обычно SELECT.
Query использует context.Background внутренне; чтобы указать контекст, используйте Tx.QueryContext.
func (*Tx) QueryContext
func (tx *Tx) QueryContext(ctx context.Context, query string, args ...any) (*Rows, error)
QueryContext выполняет запрос, который возвращает строки, обычно SELECT.
func (*Tx) QueryRow
func (tx *Tx) QueryRow(query string, args ...any) *Row
QueryRow выполняет запрос, который должен вернуть не более одной строки. QueryRow всегда возвращает значение, отличное от nil. Ошибки откладываются до вызова метода Scan Row. Если запрос не выбирает ни одной строки, *Row.Scan вернет ErrNoRows. В противном случае *Row.Scan сканирует первую выбранную строку и отбрасывает остальные.
QueryRow использует context.Background внутренне; для указания контекста используйте Tx.QueryRowContext.
func (*Tx) QueryRowContext
func (tx *Tx) QueryRowContext(ctx context.Context, query string, args ...any) *Row
QueryRowContext выполняет запрос, который должен вернуть не более одной строки. QueryRowContext всегда возвращает значение, отличное от nil. Ошибки откладываются до вызова метода Scan Row. Если запрос не выбирает ни одной строки, *Row.Scan вернет ErrNoRows. В противном случае *Row.Scan сканирует первую выбранную строку и отбрасывает остальные.
func (*Tx) Rollback
func (tx *Tx) Rollback() error
Rollback прерывает транзакцию.
Пример
код транзакции для обновления водителей и заказов
package main
import (
"context"
"database/sql"
"log"
_ "github.com/lib/pq" // Импорт драйвера PostgreSQL
)
func main() {
// Инициализация подключения к БД
connStr := "user=postgres dbname=mydb sslmode=disable"
db, err := sql.Open("postgres", connStr)
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
defer db.Close()
// Проверка соединения
if err := db.Ping(); err != nil {
log.Fatal("Database connection failed:", err)
}
// Создаем контекст с таймаутом
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Начало транзакции
tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
if err != nil {
log.Fatal("Failed to begin transaction:", err)
}
// Гарантированный откат при ошибках
defer func() {
if err := tx.Rollback(); err != nil && err != sql.ErrTxDone {
log.Printf("Warning: rollback error: %v", err)
}
}()
id := 53
// 1. Обновление статуса водителя
_, err = tx.ExecContext(ctx, "UPDATE drivers SET status = $1 WHERE id = $2", "assigned", id)
if err != nil {
log.Printf("Failed to update driver status: %v", err)
return
}
// 2. Назначение водителя на заказы
_, err = tx.ExecContext(ctx, "UPDATE pickups SET driver_id = $1 WHERE driver_id IS NULL", id)
if err != nil {
log.Printf("Failed to assign driver to pickups: %v", err)
return
}
// Фиксация транзакции
if err := tx.Commit(); err != nil {
log.Fatal("Failed to commit transaction:", err)
}
log.Printf("Successfully assigned driver %d and updated pickup orders", id)
}
Дополнительные улучшения:
-
Безопасность UPDATE:
// Ограничиваем обновление только незанятыми заказами "UPDATE pickups SET driver_id = $1 WHERE driver_id IS NULL"
-
Проверка результата:
res, err := tx.ExecContext(...) if rowsAffected, _ := res.RowsAffected(); rowsAffected == 0 { log.Printf("Warning: no rows were updated") }
-
Повторные попытки:
maxRetries := 3 for i := 0; i < maxRetries; i++ { // ... выполнение транзакции ... if err == nil { break } if shouldRetry(err) { continue } break }
-
Connection Pool:
db.SetMaxOpenConns(25) db.SetMaxIdleConns(25) db.SetConnMaxLifetime(5*time.Minute)
func (*Tx) Stmt
func (tx *Tx) Stmt(stmt *Stmt) *Stmt
Stmt возвращает подготовленное заявление, специфичное для транзакции, из существующего заявления.
Пример:
updateMoney, err := db.Prepare(«UPDATE balance SET money=money+? WHERE id=?»)
...
tx, err := db.Begin()...
res, err := tx.Stmt(updateMoney).Exec(123.45, 98293203)
Возвращенное выражение работает в рамках транзакции и будет закрыто после фиксации или отката транзакции.
Stmt использует context.Background внутри всегда; для указания контекста используйте Tx.StmtContext.
func (*Tx) StmtContext
func (tx *Tx) StmtContext(ctx context.Context, stmt *Stmt) *Stmt
StmtContext возвращает подготовленное выражение, специфичное для транзакции, из существующего выражения.
Пример:
updateMoney, err := db.Prepare(«UPDATE balance SET money=money+? WHERE id=?») … tx, err := db.Begin()…
res, err := tx.StmtContext(ctx, updateMoney).Exec(123.45, 98293203) Предоставленный контекст используется для подготовки оператора, а не для его выполнения.
Возвращенный оператор работает в рамках транзакции и будет закрыт после фиксации или отката транзакции.
type TxOptions
type TxOptions struct {
// Isolation — уровень изоляции транзакции.
// Если равен нулю, используется уровень по умолчанию драйвера или базы данных.
Isolation IsolationLevel
ReadOnly bool
}
TxOptions содержит параметры транзакции, которые будут использоваться в DB.BeginTx.