Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Примеры ошибок транзакций в Go
В Go, при работе с базами данных (чаще всего с использованием database/sql и драйверов вроде pq для PostgreSQL или go-sqlite3 для SQLite), ошибки транзакций можно разделить на несколько категорий: ошибки начала/завершения транзакции, ошибки выполнения запросов внутри транзакции и ошибки управления состоянием. Приведу практические примеры.
1. Ошибки начала и завершения транзакции
Эти ошибки возникают при попытке создать транзакцию или завершить её (через Commit() или Rollback()).
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/lib/pq"
)
func main() {
db, err := sql.Open("postgres", "user=test dbname=test sslmode=disable")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Пример: ошибка начала транзакции (например, если соединение с БД разорвано)
tx, err := db.Begin()
if err != nil {
log.Fatal("Не удалось начать транзакцию:", err) // Ошибка: может быть "connection refused"
}
// Важно: даже при ошибке Begin(), tx будет nil, поэтому проверяем err до использования tx
_, execErr := tx.Exec("INSERT INTO users (name) VALUES ($1)", "Alice")
if execErr != nil {
// Попытка отката при ошибке выполнения
if rbErr := tx.Rollback(); rbErr != nil {
log.Fatal("Ошибка отката транзакции:", rbErr) // Ошибка: например, "transaction already rolled back"
}
return
}
// Пример: ошибка коммита (например, нарушение ограничения целостности)
if err := tx.Commit(); err != nil {
log.Fatal("Ошибка коммита транзакции:", err) // Ошибка: например, "duplicate key value violates unique constraint"
}
}
2. Ошибки выполнения запросов внутри транзакции
Эти ошибки связаны с некорректными SQL-запросами, нарушениями ограничений БД или проблемами соединения.
func transferMoney(tx *sql.Tx, fromID, toID int, amount float64) error {
// Пример: ошибка из-за несуществующей таблицы или синтаксиса
_, err := tx.Exec("UPDATE accounts SET balance = balance - $1 WHERE id = $2", amount, fromID)
if err != nil {
return fmt.Errorf("ошибка списания: %w", err) // Например, "table 'accounts' does not exist"
}
// Пример: нарушение ограничения (например, отрицательный баланс)
_, err = tx.Exec("UPDATE accounts SET balance = balance + $1 WHERE id = $2", amount, toID)
if err != nil {
return fmt.Errorf("ошибка зачисления: %w", err) // Например, "CHECK constraint failed: balance >= 0"
}
// Пример: ошибка уникальности при вставке лога
_, err = tx.Exec("INSERT INTO transactions (from_id, to_id, amount) VALUES ($1, $2, $3)", fromID, toID, amount)
if err != nil {
return fmt.Errorf("ошибка записи транзакции: %w", err) // Например, "duplicate key"
}
return nil
}
3. Ошибки управления состоянием транзакции
Возникают при неправильном использовании транзакций, например, повторном коммите или использовании после завершения.
func stateErrorsExample(db *sql.DB) {
tx, _ := db.Begin()
// Пример: попытка выполнить запрос после отката
tx.Rollback()
_, err := tx.Exec("SELECT 1")
if err != nil {
log.Println("Ошибка после Rollback:", err) // Ошибка: "transaction has already been committed or rolled back"
}
// Пример: повторный коммит
tx2, _ := db.Begin()
tx2.Commit()
err = tx2.Commit() // Второй вызов Commit()
if err != nil {
log.Println("Ошибка повторного коммита:", err) // Ошибка: "sql: transaction has already been committed or rolled back"
}
// Пример: утечка транзакции (забыли Commit/Rollback)
tx3, _ := db.Begin()
_, _ = tx3.Exec("INSERT INTO logs (msg) VALUES ('test')")
// Если не вызвать tx3.Commit() или tx3.Rollback(), транзакция останется открытой, что может привести к блокировкам
}
4. Ошибки из-за изоляции и блокировок
В многопользовательских сценариях могут возникать ошибки, связанные с уровнями изоляции.
func isolationExample(db *sql.DB) {
// Установка уровня изоляции (например, Repeatable Read)
opts := &sql.TxOptions{Isolation: sql.LevelRepeatableRead}
tx, _ := db.BeginTx(context.Background(), opts)
// Пример: сериализационная ошибка в PostgreSQL при конкурентном доступе
_, err := tx.Exec("UPDATE counters SET value = value + 1 WHERE id = 1")
if err != nil {
// В PostgreSQL может вернуться "serialization failure" или "deadlock detected"
log.Println("Ошибка сериализации/взаимоблокировки:", err)
// В таком случае обычно нужно повторить транзакцию
}
}
5. Распространённые антипаттерны и как их избежать
- Пропуск проверки ошибок от Rollback(): Всегда проверяйте ошибку при откате.
if err := tx.Rollback(); err != nil && err != sql.ErrTxDone { log.Println("Неожиданная ошибка Rollback:", err) } - Использование глобальных переменных для транзакций: Транзакции должны быть локальными для функции/операции.
- Длительные транзакции: Избегайте выполнения долгих операций внутри транзакции, чтобы не блокировать другие запросы.
Ключевые выводы:
- Всегда проверяйте ошибки после
Begin(),Exec(),Query(),Commit()иRollback(). - Используйте откат при любой ошибке внутри транзакции, чтобы избежать «висящих» транзакций.
- Контекст (
context.Context) помогает управлять таймаутами и отменами транзакций. - Повторяйте транзакции при ошибках сериализации (deadlock, serialization failure), характерных для высоконагруженных систем.