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

Как отменить транзакцию?

1.3 Junior🔥 251 комментариев
#Базы данных

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

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

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

Отмена транзакции в Go

Отмена транзакции в Go зависит от типа транзакции и используемой базы данных, но общий принцип заключается в использовании методов Rollback() или Commit() интерфейса sql.Tx. Ключевой момент: отмена (rollback) транзакции должна выполняться явно в случае ошибки.

Базовый паттерн работы с транзакциями

В Go стандартный подход использует defer для гарантированного выполнения отката, если транзакция не была зафиксирована. Вот типичный пример:

package main

import (
    "database/sql"
    "fmt"
    "log"
    _ "github.com/lib/pq"
)

func transferMoney(db *sql.DB, from, to string, amount int) error {
    // Начинаем транзакцию
    tx, err := db.Begin()
    if err != nil {
        return fmt.Errorf("begin transaction failed: %w", err)
    }
    
    // Гарантируем откат при панике или если commit не выполнится
    defer tx.Rollback()
    
    // Выполняем операции в транзакции
    _, err = tx.Exec("UPDATE accounts SET balance = balance - $1 WHERE id = $2", amount, from)
    if err != nil {
        return fmt.Errorf("subtract from sender failed: %w", err)
    }
    
    _, err = tx.Exec("UPDATE accounts SET balance = balance + $1 WHERE id = $2", amount, to)
    if err != nil {
        return fmt.Errorf("add to receiver failed: %w", err)
    }
    
    // Если все операции успешны - фиксируем изменения
    if err := tx.Commit(); err != nil {
        return fmt.Errorf("commit failed: %w", err)
    }
    
    // Успешный commit отменяет defer tx.Rollback()
    return nil
}

Важные аспекты отмены транзакций

  1. Неявный Rollback через defer

    • Использование defer tx.Rollback() гарантирует откат при любом выходе из функции
    • После успешного Commit() вызов Rollback() возвращает ошибку sql.ErrTxDone, которую можно игнорировать
  2. Контекст и отмена транзакций

    • Современные версии Go поддерживают транзакции с контекстом:
    tx, err := db.BeginTx(ctx, &sql.TxOptions{
        Isolation: sql.LevelSerializable,
        ReadOnly:  false,
    })
    
    • Контекст позволяет отменить долгие операции, но сама отмена транзакции всё равно требует явного вызова Rollback()
  3. Особенности разных СУБД

    • PostgreSQL: Транзакции откатываются полностью
    • MySQL: Поддерживает savepoints для частичного отката:
    tx.Exec("SAVEPOINT sp1")
    // какие-то операции
    tx.Exec("ROLLBACK TO SAVEPOINT sp1")
    

Распространённые ошибки и лучшие практики

  • Не игнорировать ошибки Rollback: Всегда проверяйте ошибку, хотя бы для логирования:

    if err := tx.Rollback(); err != nil && err != sql.ErrTxDone {
        log.Printf("rollback error: %v", err)
    }
    
  • Изоляция и deadlock: При использовании высоких уровней изоляции увеличивается вероятность deadlock. Некоторые драйверы автоматически повторяют транзакцию при deadlock.

  • Таймауты: Устанавливайте разумные таймауты через контекст для предотвращения висящих транзакций.

  • Не смешивать транзакционные и нетранзакционные запросы: Используйте только tx.Exec(), tx.Query() внутри транзакции, а не исходный db.Exec().

Альтернативные подходы

  1. Ручное управление без defer: В некоторых случаях (очень долгие транзакции) может быть предпочтительнее явный вызов Rollback() в каждом месте выхода из функции.

  2. Использование библиотек высшего уровня: ORM вроде Gorm предоставляют свои методы управления транзакциями:

    db.Transaction(func(tx *gorm.DB) error {
        // автоматический commit/rollback
    })
    

Вывод: Отмена транзакции в Go выполняется методом Rollback() объекта sql.Tx. Паттерн с defer tx.Rollback() считается идиоматическим и безопасным, так как гарантирует откат при любом сценарии, кроме успешного коммита. Важно помнить, что в распределённых системах отмена транзакции может иметь сложные последствия, особенно при работе с несколькими микросервисами или базами данных.

Как отменить транзакцию? | PrepBro