Как транзакции обеспечивают консистентность данных
Что проверяют
Знаете ли вы, какие механизмы стоят за ACID и где транзакция перестаёт помогать. Для Go-разработчика следом спрашивают про database/sql: как не забыть откат, не держать соединение и что делать с конкурентными обновлениями.
Как отвечать
Консистентность держится на трёх других свойствах и ограничениях схемы:
- атомарность: все операции транзакции применяются вместе или не применяются вовсе;
- согласованность: база проверяет
CHECK,UNIQUE, внешние ключи и не даёт зафиксировать нарушение. Правило, которого нет в схеме, остаётся на совести кода; - изоляция: определяет, что транзакция видит из параллельных. В PostgreSQL по умолчанию
READ COMMITTED, в MySQL с InnoDBREPEATABLE READ; - долговечность: после
COMMITзапись уже в журнале на диске и переживёт падение.
В Go каркас такой: tx, err := db.BeginTx(ctx, opts), сразу defer tx.Rollback(), затем запросы через tx.ExecContext и в конце tx.Commit(). После успешного коммита Rollback вернёт sql.ErrTxDone, это безопасно. Уровень изоляции задаётся через sql.TxOptions{Isolation: sql.LevelSerializable}. В pgx то же самое делает BeginTx, а хелпер pgx.BeginFunc сам коммитит или откатывает по результату функции.
На примере перевода денег покажите не только «оба шага или ни одного», но и гонку: два перевода параллельно читают один баланс. Решение: SELECT ... FOR UPDATE по строкам счетов, причём всегда в одном порядке, чтобы не словить взаимоблокировку, либо SERIALIZABLE с повтором транзакции при ошибке сериализации (SQLSTATE 40001).
Где спотыкаются
Внутри транзакции делают запрос через db, а не через tx. Он уходит по другому соединению из пула, в транзакцию не попадает и не видит её незафиксированных изменений.
Забывают defer tx.Rollback() на ветке с ошибкой. Соединение остаётся занятым открытой транзакцией, и под нагрузкой пул быстро заканчивается.
Смешивают ACID и распределённые системы. Транзакция одной базы не делает атомарными запись в базу и отправку сообщения в брокер, для этого нужен transactional outbox или саги.