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

Зачем нужны транзакции?

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

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

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

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

Зачем нужны транзакции

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

Основная проблема

Представь финансовый перевод денег:

// БЕЗ транзакции:
db.Exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
db.Exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2")

Что может пойти не так:

  • Первая операция успешна
  • Приложение падает или отключается
  • Вторая операция никогда не выполнится
  • Результат: деньги исчезли! Нарушена консистентность данных

ACID гарантии

Транзакции обеспечивают ACID — четыре свойства:

A — Atomicity (Атомарность):

  • Транзакция либо полностью выполняется, либо полностью откатывается
  • Нет промежуточных состояний
  • Перевод денег либо произойдёт полностью, либо не произойдёт вообще
db.WithTx(func(tx *sql.Tx) error {
    // все операции будут выполнены вместе или откачены вместе
    tx.Exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
    tx.Exec("UPDATE accounts SET balance = balance + 100 WHERE id = 2")
    return nil // если вернём ошибку, всё откатится
})

C — Consistency (Консистентность):

  • База данных переходит из одного согласованного состояния в другое
  • Инварианты приложения сохраняются
  • Например: сумма денег во всех счётах всегда одна и та же

I — Isolation (Изоляция):

  • Одновременные транзакции не мешают друг другу
  • Каждая видит консистентный снимок данных
  • Уровни изоляции контролируют, насколько строго это выполняется

D — Durability (Долговечность):

  • После успешного завершения транзакции данные сохранены на диск
  • Если произойдёт сбой, данные не будут потеряны

Практические примеры

1. Банковские операции

func transferMoney(from, to string, amount float64) error {
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    defer tx.Rollback() // откатится, если не вызвана Commit
    
    // Снять со счёта
    if _, err := tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, from); err != nil {
        return err
    }
    
    // Добавить на счёт
    if _, err := tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, to); err != nil {
        return err
    }
    
    // Если всё ОК, фиксируем
    return tx.Commit().Error
}

2. Создание заказа с товарами

func createOrder(userID string, items []Item) error {
    tx := db.BeginTx(ctx, nil)
    
    // Создать заказ
    var orderID string
    if err := tx.QueryRow("INSERT INTO orders (user_id) VALUES (?) RETURNING id", userID).Scan(&orderID); err != nil {
        tx.Rollback()
        return err
    }
    
    // Добавить товары
    for _, item := range items {
        if _, err := tx.Exec("INSERT INTO order_items (order_id, item_id, qty) VALUES (?, ?, ?)", orderID, item.ID, item.Qty); err != nil {
            tx.Rollback()
            return err
        }
    }
    
    // Если всё успешно, фиксируем транзакцию
    return tx.Commit().Error
}

Проблемы без транзакций

Грязное чтение:

  • Process A пишет данные
  • Process B читает незавершённые данные
  • Process A откатывается
  • Process B работает с неправильными данными

Lost Update:

  • Process A читает значение (10)
  • Process B читает значение (10)
  • Process A увеличивает на 5, пишет (15)
  • Process B увеличивает на 3, пишет (13)
  • Результат: потеряно изменение Process A

Broken Referential Integrity:

  • Удаляем заказ
  • Забываем удалить товары заказа
  • База в несогласованном состоянии

Применение в Go

// с database/sql
tx, _ := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
defer tx.Rollback()

// работа с транзакцией
tx.ExecContext(ctx, ...)
tx.QueryRowContext(ctx, ...)

tx.Commit() // или откатим

// с ORM (GORM)
db.WithTx(tx).Model(&Order{}).Create(&order)
db.Transaction(func(tx *gorm.DB) error {
    // код
    return nil
})

Когда транзакции обязательны

  • Финансовые операции — переводы, платежи
  • Изменение нескольких таблиц — должны быть вместе
  • Проверка бизнес-инвариантов — уникальные ключи, constraints
  • Операции с зависимостями — parent-child отношения

Когда транзакции опциональны

  • Чтение данных (SELECT)
  • Логирование — если можем потерять запись
  • Кеширование — не критично по консистентности
  • Статистика — небольшие неточности допустимы

Best Practices

DO:

  • Используй транзакции для мульти-операционных изменений
  • Выбирай правильный уровень изоляции
  • Держи транзакции короткими (меньше конфликтов)
  • Логируй ошибки откатов

DON'T:

  • Не держи транзакцию открытой долго
  • Не делай HTTP запросы внутри транзакции
  • Не полагайся только на откат при ошибках

Вывод: транзакции — это критически важный инструмент для гарантирования надежности и консистентности данных. Без них современные приложения неработоспособны.