Является ли одиночный update транзакцией
Что проверяют
Знаете ли вы, что такое неявная транзакция и автокоммит. Вопрос отсеивает и тех, кто считает транзакцией только явные BEGIN и COMMIT в коде, и тех, кто на всякий случай оборачивает в транзакцию каждый запрос.
Как отвечать
Да, является. В PostgreSQL и в MySQL с InnoDB любой оператор, выполненный вне явной транзакции, выполняется в неявной: сервер сам открывает её перед оператором и фиксирует после. Один UPDATE, задевший миллион строк, применится целиком или не применится вовсе, и соседняя сессия никогда не увидит половину изменений.
Значит, явная транзакция нужна не ради атомарности одного запроса, а когда:
- несколько операторов должны примениться как одно целое;
- между чтением и записью нельзя допустить чужую правку, и нужен
SELECT ... FOR UPDATE; - несколько чтений должны видеть один снимок данных.
В Go это видно прямо в API: db.ExecContext идёт в режиме автокоммита, а db.BeginTx занимает соединение до Commit или Rollback. Отложенный tx.Rollback() ставится сразу после BeginTx: после успешной фиксации он вернёт ошибку завершённой транзакции, и это нормально, зато при любом раннем выходе соединение не подвиснет.
Стоит добавить, что счётчик вида UPDATE t SET n = n + 1 WHERE id = $1 атомарен на уровне оператора, а те же действия, разложенные на SELECT и UPDATE в коде, уже требуют либо транзакции с блокировкой, либо оптимистической проверки версии.
Где спотыкаются
Отвечают «нет, транзакцию надо открыть явно» и путают атомарность с явным управлением. Атомарность даёт движок, BEGIN нужен для группировки.
Заворачивают каждый одиночный запрос в транзакцию ради надёжности. Выигрыша нет, зато соединение держится дольше, а на пуле под нагрузкой это заметно.
Забывают Rollback на ветке с ошибкой или делают его без defer. Соединение остаётся занятым, пул исчерпывается, и приложение встаёт целиком, хотя база при этом здорова.