Писали ли транзакции в SQL
Что проверяют
Работали ли вы с транзакцией руками и понимаете ли, что она занимает соединение из пула на всё время жизни. В Go на этом вопросе хорошо видно разницу между «читал про ACID» и «чинил зависший пул».
Как отвечать
Покажите обычный каркас и объясните каждую строку:
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
_, err = tx.ExecContext(ctx,
`update accounts set balance = balance - $1 where id = $2`, sum, from)
if err != nil {
return err
}
return tx.Commit()defer tx.Rollback() после успешного подтверждения просто вернёт ошибку о завершённой транзакции, поэтому его ставят всегда. Ошибку Commit обязательно проверяют: именно там всплывает конфликт сериализации. Дальше уместно сказать про уровни изоляции: по умолчанию в PostgreSQL действует Read Committed, а на более строгих уровнях приложение обязано уметь повторить транзакцию после отказа. И про контекст с таймаутом, чтобы зависший запрос не держал соединение бесконечно.
Где спотыкаются
Внутри транзакции зовут метод самого пула вместо метода транзакции. Называются они одинаково, компилятор молчит, а запрос уходит по другому соединению и остаётся за границами транзакции.
Забывают откат на раннем возврате по ошибке. Транзакция висит, соединение в пул не возвращается, и после нескольких таких ошибок сервис перестаёт отвечать целиком, хотя база в порядке.
Читают значение в переменную, считают в коде и пишут готовое число обратно. Между чтением и записью строку успевает поменять соседняя транзакция, и её изменение теряется молча. Нужна либо арифметика прямо в запросе, либо блокировка строки на чтение, либо повтор при конфликте.