← Назад к вопросам

Приведи примеры ошибок транзакций

2.0 Middle🔥 141 комментариев
#Основы Go

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Примеры ошибок транзакций в 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), характерных для высоконагруженных систем.
Приведи примеры ошибок транзакций | PrepBro