Что такое транзакционность в PostgreSQL
Что проверяют
Понимаете ли вы, что гарантирует база сама, а что остаётся на коде сервиса: где открывается граница, кто делает откат, как приложение ведёт себя при конфликте параллельных транзакций.
Как отвечать
Транзакционность это выполнение группы операторов между BEGIN и COMMIT как одного целого: до фиксации изменения не видны другим, при откате исчезают. Без явного начала каждый запрос сам себе транзакция.
Механика PostgreSQL: версионность строк (MVCC), при которой читатели не блокируют писателей и наоборот, а конкурирующая запись в одну строку всё же ждёт; журнал упреждающей записи, который даёт долговечность на фиксации. Уровни изоляции: по умолчанию read committed, где каждый запрос видит свежий снимок; repeatable read, где снимок один на всю транзакцию; serializable с проверкой сериализуемости. На двух верхних возможна ошибка сериализации, и повтор транзакции обязан делать код.
tx, err := db.BeginTx(ctx, nil)
if err != nil { return err }
defer tx.Rollback()
if _, err = tx.ExecContext(ctx, q, args...); err != nil { return err }
return tx.Commit()Отложенный откат после успешной фиксации безвреден, он вернёт ошибку о завершённой транзакции.
Где спотыкаются
Не знают про прерванную транзакцию. После первой же ошибки PostgreSQL переводит транзакцию в состояние отказа, и любой следующий запрос падает с сообщением о том, что команды игнорируются до конца блока. Продолжить с места ошибки можно только через точку сохранения, иначе остаётся откат.
Теряют соединение. Транзакция занимает соединение пула целиком, поэтому забытый откат при раннем выходе быстро исчерпывает пул и сервис встаёт. Вторая версия той же ошибки: часть запросов внутри границы уходит не в объект транзакции, а в общий пул, то есть выполняется другим соединением и вне транзакции, иногда с самоблокировкой.
Проверяют условие отдельным запросом и на основании результата вставляют строку, полагая, что уровня read committed достаточно. Между чтением и записью успевает вклиниться соседний процесс. Нужны уникальный индекс, вставка с обработкой конфликта или блокировка строки на чтение, а на верхних уровнях изоляции ещё и повтор по ошибке сериализации.