Что такое ACID и как обеспечить транзакционность в Go при работе с базами данных?

Ответ

ACID — это акроним, описывающий четыре ключевых свойства, которые гарантируют надежность транзакций в базе данных:

  • Atomicity (Атомарность): Транзакция выполняется либо полностью, либо не выполняется вовсе. Не может быть промежуточного состояния. Если один из запросов внутри транзакции завершился ошибкой, все предыдущие изменения откатятся.
  • Consistency (Согласованность): Транзакция переводит базу данных из одного согласованного состояния в другое. Все правила и ограничения (constraints, triggers) базы данных должны быть соблюдены.
  • Isolation (Изолированность): Параллельно выполняющиеся транзакции не должны влиять друг на друга. Результат параллельных транзакций должен быть таким же, как если бы они выполнялись последовательно.
  • Durability (Долговечность): Если транзакция успешно завершена (закоммичена), ее изменения сохраняются в базе данных навсегда, даже в случае сбоя системы (например, отключения питания).

Реализация в Go

В Go для работы с транзакциями в SQL-базах данных используется стандартный пакет database/sql и объект sql.Tx.

Алгоритм работы с транзакцией:

  1. Начать транзакцию с помощью db.Begin() или db.BeginTx().
  2. Выполнять все запросы (INSERT, UPDATE, DELETE) в рамках этой транзакции, используя методы объекта tx (tx.ExecContext, tx.QueryRowContext и т.д.).
  3. Если на любом из шагов возникает ошибка, необходимо откатить транзакцию с помощью tx.Rollback().
  4. Если все операции прошли успешно, транзакцию нужно подтвердить с помощью tx.Commit().

Пример кода:

// ctx - контекст, db - установленное соединение с БД
func transferMoney(ctx context.Context, db *sql.DB, fromID, toID int64, amount int) error {
    // 1. Начинаем транзакцию
    tx, err := db.BeginTx(ctx, nil)
    if err != nil {
        return fmt.Errorf("could not begin transaction: %w", err)
    }
    // 3. Откатываем транзакцию в случае ошибки (defer гарантирует вызов)
    defer tx.Rollback() // Rollback будет проигнорирован, если tx.Commit() выполнится успешно

    // 2. Выполняем операции
    _, err = tx.ExecContext(ctx, "UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, fromID)
    if err != nil {
        return fmt.Errorf("failed to withdraw: %w", err)
    }

    _, err = tx.ExecContext(ctx, "UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, toID)
    if err != nil {
        return fmt.Errorf("failed to deposit: %w", err)
    }

    // 4. Если все успешно, коммитим транзакцию
    if err := tx.Commit(); err != nil {
        return fmt.Errorf("failed to commit transaction: %w", err)
    }

    return nil
}

Использование defer tx.Rollback() — это идиоматический и безопасный способ гарантировать откат транзакции при любой ошибке или панике в функции.