Что такое вложенные транзакции и как их реализовать в Go?

Ответ

Вложенная транзакция — это транзакция, которая начинается внутри уже существующей (родительской) транзакции. Это позволяет создавать изолированные точки для отката части операций, не затрагивая всю родительскую транзакцию.

Стандартный пакет database/sql в Go не поддерживает вложенные транзакции напрямую, так как он предоставляет общий интерфейс, а не все СУБД имеют такую функциональность.

Эмуляция с помощью точек сохранения (Savepoints)

Наиболее распространенный способ эмулировать вложенные транзакции — использовать точки сохранения (savepoints), которые поддерживаются многими СУБД (например, PostgreSQL, MySQL, SQLite).

SAVEPOINT создает именованную точку внутри транзакции, к которой можно позже откатиться с помощью ROLLBACK TO SAVEPOINT.

Пример реализации

// tx - это уже начатая транзакция *sql.Tx

// Начало "вложенной" транзакции
_, err := tx.Exec("SAVEPOINT my_savepoint")
if err != nil {
    log.Fatalf("Failed to create savepoint: %v", err)
}

// Выполняем какие-то операции внутри "вложенной" транзакции
_, err = tx.Exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1")
if err != nil {
    // Если произошла ошибка, откатываемся к точке сохранения
    log.Printf("Operation failed, rolling back to savepoint: %v", err)
    _, rollbackErr := tx.Exec("ROLLBACK TO SAVEPOINT my_savepoint")
    if rollbackErr != nil {
        log.Fatalf("Failed to rollback to savepoint: %v", rollbackErr)
    }
} else {
    // Если все успешно, можно "закоммитить" изменения, удалив точку сохранения
    // В PostgreSQL это не обязательно, но в некоторых СУБД может быть полезно
    _, releaseErr := tx.Exec("RELEASE SAVEPOINT my_savepoint")
    if releaseErr != nil {
        log.Printf("Failed to release savepoint: %v", releaseErr)
    }
}

// В конце родительская транзакция коммитится или откатывается целиком
err = tx.Commit()
if err != nil {
    log.Fatalf("Failed to commit transaction: %v", err)
}

Важно

  • Некоторые драйверы (например, pgx для PostgreSQL) предоставляют более удобные абстракции для работы с точками сохранения.
  • Синтаксис SAVEPOINT является стандартным для SQL, но всегда лучше сверяться с документацией вашей СУБД.