> Является ли одиночный update транзакцией (Go)

Уровень: senior · Роль: backend · Категория: Технические вопросы

Компании: VK

Стек: Go

> Пример ответа

Короткий ответ

Да, одиночный UPDATE в PostgreSQL выполняется в рамках неявной транзакции. Каждый отдельный SQL-оператор автоматически оборачивается в транзакцию, которая либо полностью применяется, либо полностью откатывается при ошибке. Это гарантирует атомарность: строка не может остаться в промежуточном состоянии. В Go с database/sql это поведение сохраняется, если вы не используете явные BEGIN/COMMIT для группировки нескольких операторов.

Подробное объяснение

В PostgreSQL (и большинстве реляционных СУБД) действует принцип авто-коммита: каждый одиночный оператор выполняется внутри неявной транзакции. Это означает:

  • Если UPDATE завершается успешно - изменения фиксируются немедленно.
  • Если происходит ошибка (нарушение constraint, deadlock, таймаут) - все изменения, сделанные этим оператором, откатываются.

Важно понимать разницу между атомарностью одиночного оператора и транзакцией как таковой:

  1. Одиночный UPDATE - атомарен сам по себе. Даже если он затрагивает 1000 строк и падает на 500-й, первые 499 изменений будут отменены.

  2. Множественные операторы - если вам нужно выполнить несколько UPDATE или UPDATE + INSERT как единое целое, требуется явная транзакция через BEGIN/COMMIT (или tx в Go).

В PostgreSQL есть нюанс: если внутри транзакции происходит ошибка, транзакция переходит в "aborted" состояние, и все последующие операторы до ROLLBACK будут отклонены. Для одиночного UPDATE это неактуально, так как неявная транзакция завершается сразу.

На практике

В Go при работе с database/sql:

  • db.Exec("UPDATE ...") - выполняется в неявной транзакции, атомарно.
  • Если вы используете db.Begin(), tx.Exec("UPDATE ...") и tx.Commit() - это уже явная транзакция, которая может включать несколько операторов.

Важно: при использовании пула соединений (sql.DB) каждый вызов Exec может выполняться на разных соединениях, но это не влияет на атомарность одиночного оператора - неявная транзакция всегда привязана к конкретному соединению.

Для проверки атомарности можно использовать RETURNING или проверять RowsAffected, но это не заменяет понимания транзакционной модели.

Пример кода

GO
// Одиночный UPDATE - атомарен, неявная транзакция
_, err := db.Exec(
"UPDATE users SET balance = balance - 100 WHERE id = $1",
userID,
)
if err != nil {
// изменения откатятся автоматически
return err
}
// Явная транзакция для нескольких операторов
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback() // безопасный откат при ошибке
_, err = tx.Exec("UPDATE accounts SET amount = amount - 100 WHERE id = $1", fromID)
if err != nil {
return err // tx.Rollback() сработает через defer
}
_, err = tx.Exec("UPDATE accounts SET amount = amount + 100 WHERE id = $1", toID)
if err != nil {
return err
}
return tx.Commit() // только теперь изменения фиксируются

Как отвечать на собеседовании

Начните с прямого ответа: "Да, одиночный UPDATE выполняется в неявной транзакции". Затем поясните разницу между атомарностью оператора и явной транзакцией. Упомяните, что в PostgreSQL это называется авто-коммит. Приведите пример из Go, показывающий, когда нужна явная транзакция (несколько операторов). Если спросят про уровень изоляции - скажите, что для одиночного оператора действует уровень изоляции по умолчанию (Read Committed), и это не влияет на атомарность.

Что проверяет интервьюер

  • Понимание базовой транзакционной модели СУБД.
  • Умение различать атомарность оператора и транзакцию.
  • Знание поведения при ошибках (rollback).
  • Практические навыки работы с database/sql в Go.
  • Понимание, когда нужна явная транзакция, а когда достаточно одиночного оператора.

Типичные ошибки

  • Утверждение, что одиночный UPDATE не является транзакцией - это неверно.
  • Смешивание понятий "атомарность" и "изоляция" - это разные свойства ACID.
  • Игнорирование состояния aborted после ошибки в явной транзакции.
  • Предположение, что RowsAffected == 0 означает ошибку - это не так, оператор может быть успешным, но не затронуть строк.
  • Забывание про defer tx.Rollback() в Go - при ошибке до Commit соединение останется в транзакции.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью