Как транзакции обеспечивают консистентность данных
Что проверяют
Понимаете ли вы, что ACID это не четыре слова из учебника, а конкретные механизмы базы, и где кончается ответственность транзакции. Следом почти всегда спрашивают про уровни изоляции и аномалии, а на Python-собеседовании ещё и про то, как транзакцией управляет драйвер или ORM.
Как отвечать
Транзакция делает несколько операций одним изменением. Консистентность получается не сама по себе, её собирают три других свойства плюс ограничения схемы:
- атомарность: применяются все изменения или ни одного. При ошибке
ROLLBACKвозвращает всё назад, в PostgreSQL это держится на журнале WAL и MVCC; - согласованность: база проверяет
CHECK,UNIQUE,FOREIGN KEYи не даёт зафиксировать нарушение. Бизнес-правило, которого нет в схеме, база не защитит; - изоляция: параллельные транзакции не видят чужих незафиксированных данных. В PostgreSQL по умолчанию
READ COMMITTED, от потерянного обновления он не спасает, для этого естьSELECT ... FOR UPDATEилиSERIALIZABLEс повтором при ошибке сериализации; - долговечность: после
COMMITданные переживут падение сервера, запись в журнал уже сброшена на диск.
Теперь про Python. По PEP 249 драйвер открывает транзакцию неявно, и без commit() изменения пропадут. В psycopg 3 соединение как контекстный менеджер коммитит при успешном выходе и откатывает при исключении. В SQLAlchemy 2.0 это with Session(engine) as session, session.begin():, в Django transaction.atomic(), а по умолчанию там autocommit на каждый запрос.
Пример с переводом между счетами доведите до конца: списание и зачисление в одной транзакции плюс блокировка строк, чтобы два перевода не прочитали один старый баланс.
Где спотыкаются
Путают C из ACID с C из теоремы CAP. Первое про ограничения внутри одной базы, второе про согласованность реплик в распределённой системе.
Считают, что транзакция сама решает гонки. На READ COMMITTED два параллельных «прочитал баланс, вычел, записал» дадут потерянное обновление без всякой ошибки.
Кладут в транзакцию HTTP-запрос к внешнему сервису. Откат не отменит уже отправленный запрос, а долгая транзакция держит блокировки и мешает очистке старых версий строк.