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

Как начать транзакцию?

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

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

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

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

Как начать транзакцию в Go

В Go для работы с транзакциями используется стандартный пакет database/sql, который предоставляет унифицированный интерфейс для различных SQL-драйверов. Процесс зависит от используемого драйвера базы данных (MySQL, PostgreSQL, SQLite, etc.), но общий API остается неизменным.

Основные шаги для начала транзакции

  1. Получение объекта транзакции: вызываем метод Begin() или BeginTx() у подключения к базе данных (*sql.DB).
  2. Выполнение операций: используем методы Exec(), Query(), QueryRow() у объекта транзакции.
  3. Фиксация или откат: завершаем транзакцию через Commit() или Rollback().

Простой пример с Begin()

package main

import (
    "database/sql"
    "fmt"
    "log"
    _ "github.com/lib/pq" // Драйвер PostgreSQL
)

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)
    }

    // Выполняем операции внутри транзакции
    _, err = tx.Exec("INSERT INTO users(name) VALUES($1)", "Alice")
    if err != nil {
        // Откат при ошибке
        tx.Rollback()
        log.Fatal(err)
    }

    _, err = tx.Exec("UPDATE accounts SET balance = balance - 100 WHERE user_id = 1")
    if err != nil {
        tx.Rollback()
        log.Fatal(err)
    }

    // Фиксируем изменения
    err = tx.Commit()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Транзакция успешно завершена")
}

Контроль транзакций с BeginTx()

Метод BeginTx() предоставляет больше контроля через параметр *sql.TxOptions:

import "database/sql"

func beginTxExample(db *sql.DB) {
    // Определяем параметры транзакции
    opts := &sql.TxOptions{
        Isolation: sql.LevelSerializable, // Уровень изоляции
        ReadOnly:  false,                 // Только для чтения
    }

    tx, err := db.BeginTx(context.Background(), opts)
    if err != nil {
        log.Fatal(err)
    }

    // Использование контекста позволяет:
    // - Установить таймаут для транзакции
    // - Прервать выполнение при необходимости
    // - Передать метки и значения для трассировки

    // Пример с таймаут
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    txWithTimeout, err := db.BeginTx(ctx, nil)
    if err != nil {
        log.Fatal(err)
    }
}

Уровни изоляции транзакций

Go поддерживает стандартные уровни изоляции SQL через sql.IsolationLevel:

  • sql.LevelDefault: уровень по умолчанию драйвера
  • sql.LevelReadUncommitted: чтение незафиксированных данных
  • sql.LevelReadCommitted: чтение только фиксированных данных
  • sql.LevelWriteCommitted: (редко используется)
  • sql.LevelRepeatableRead: гарантия повторяемости чтения
  • sql.LevelSerializable: полная сериализация транзакций

Критические рекомендации

  • Всегда обрабатывайте откат: при любой ошибке внутри транзакции вызывайте Rollback().
  • Не забывайте фиксировать: если транзакция успешна, обязательно выполните Commit().
  • Используйте defer для Rollback: это гарантирует очистку даже при панике.
tx, err := db.Begin()
if err != nil {
    return err
}
// Гарантированный откат при выходе из функции до Commit
defer tx.Rollback()

// Основные операции
if err := performOperations(tx); err != nil {
    // Rollback уже будет вызван defer
    return err
}

// Фиксация только при успехе
return tx.Commit()

Особенности для разных баз данных

  • MySQL: драйвер github.com/go-sql-driver/mysql автоматически переводит уровни изоляции в соответствующие SQL.
  • PostgreSQL: драйвер github.com/lib/pq полностью поддерживает BeginTx() с уровнями изоляции.
  • SQLite: драйвер github.com/mattn/go-sqlite3 может иметь ограничения по параллельным транзакциям.

Практический паттерн: транзакция с возвратом результата

func createUserWithTransaction(db *sql.DB, name string) (int64, error) {
    tx, err := db.Begin()
    if err != nil {
        return 0, err
    }
    defer tx.Rollback()

    res, err := tx.Exec("INSERT INTO users(name) VALUES(?)", name)
    if err != nil {
        return 0, err
    }

    id, err := res.LastInsertId()
    if err != nil {
        return 0, err
    }

    err = tx.Commit()
    if err != nil {
        return 0, err
    }

    return id, nil
}

Обработка ошибок и восстановление

Важно понимать, что после Rollback() или Commit() объект транзакции становится непригодным для использования. Все дальнейшие вызовы методов вернут ошибку. Поэтому типичная структура:

tx, err := db.Begin()
if err != nil {
    // Ошибка начала транзакции
    return err
}

defer func() {
    if p := recover(); p != nil {
        // Паника -> откат
        tx.Rollback()
        panic(p)
    }
}()

// Бизнес-логика
err = businessLogic(tx)
if err != nil {
    // Ошибка логики -> откат через defer
    return err
}

// Успех -> фиксация
return tx.Commit()

Заключение

Начать транзакцию в Go — это простой процесс через db.Begin() или db.BeginTx(). Ключевые моменты:

  1. Используйте defer для Rollback для безопасного завершения
  2. Контролируйте уровень изоляции через TxOptions при необходимости
  3. Всегда фиксируйте или откатывайте — незавершенные транзакции могут блокировать ресурсы
  4. Учитывайте особенности драйвера — некоторые базы данных имеют специфичные поведения

Правильное управление транзакциями обеспечивает консистентность данных, изоляцию операций и отказоустойчивость приложения.

Как начать транзакцию? | PrepBro