Перейти к содержанию

Создание диалекта: транзакции

Шаг 7 создания диалекта: транзакции, точки сохранения и конкурентный доступ.

7. Транзакции и одновременная работа

Features.supports_transactions решает, есть ли транзакции вообще (см. Features).

Сама транзакция — один автомат состояний, написанный один раз в TransactionClient (hare.dialects.base.client): begin(), commit(), rollback(), savepoint(), release_savepoint() и savepoint_rollback(), вложенность, обработчики on_commit()/on_rollback(), защита COMMIT от отмены задачи, отказ выполнять команду после окончания транзакции и сообщения TransactionEvent. Клиент транзакции драйвера даёт только примитивы, которые автомат вызывает:

Примитив Что делает
_driver_begin(), _driver_commit(), _driver_rollback() Начинает транзакцию на соединении; отправляет её COMMIT; отправляет её ROLLBACK.
_driver_savepoint(name), _driver_release_savepoint(name), _driver_rollback_to_savepoint(name) Открывает точку сохранения, освобождает её и откатывается к ней.
_get_new_savepoint_name() Имя точки сохранения, ещё не использованное на этом соединении.
_is_connection_lost(error) Означает ли ошибка драйвера при COMMIT/ROLLBACK, что соединение пропало, — такую транзакцию сервер откатывает. По умолчанию False.
_is_commit_outcome_unknown(error) Остаётся ли при потере соединения неизвестным, прошёл ли выполнявшийся COMMIT.
_is_commit_rejection(error) Ответила ли база на COMMIT ошибкой — транзакция закончена.
_is_transaction_finished_error(error) Сообщает ли драйвер, что более ранний, прерванный COMMIT/ROLLBACK уже закончил транзакцию.
_check_commit_allowed(), _check_savepoint_allowed(), _before_top_level_end(event), _after_rejected_commit(error) Необязательные точки расширения вокруг шагов автомата: SQLite отказывается фиксировать прерванную транзакцию и возвращает транзакцию «только чтение» в обычный режим перед её окончанием.
_take_transaction_resources(), _give_back_transaction_resources() Забирают то, что транзакция верхнего уровня держит всю свою жизнь, до BEGIN — соединение из пула, блокировку соединения — и возвращают сразу, как только COMMIT/ROLLBACK выполнен, до обратных вызовов. По умолчанию ничего не делают.
_undo_failed_begin() Завершает транзакцию, чей BEGIN упал, но мог успеть выполниться. По умолчанию ничего не делает.
_end_unfinished_transaction() Завершает то, что транзакция верхнего уровня оставила открытым у драйвера, когда её блок закончился. По умолчанию ничего не делает.

Ставьте begins_without_suspending = True только если _driver_begin() подготавливает отложенное состояние, ни разу не приостанавливаясь. Такая подготовка выполняется напрямую; сам отложенный BEGIN по-прежнему должен идти через защищённый путь PendingStatements. По умолчанию False: тогда защищён от отмены сам _driver_begin().

Шесть методов _driver_* и _get_new_savepoint_name() абстрактные; у остальных есть реализация по умолчанию. Вложенная транзакция — клиент того же класса на том же соединении; его создаёт базовый класс.

Клиент, который сам применяет lock_timeout транзакции, — таймаут занятости SQLite — ставит enforces_lock_timeout_itself = True, и база без команды для него тогда принимает эту опцию.

TransactionStatements диалекта (build_transactions()) настраивают транзакцию:

Член класса Что задаёт
get_isolation_level(requested) Уровень, на котором выполняется транзакция, попросившая уровень, — самый слабый из Features.isolation_levels, который не слабее запрошенного.
get_isolation_level_sql(level) Команду, задающую его; None, если каждая транзакция и так выполняется на этом уровне.
get_read_only_sql() Команду, делающую транзакцию «только чтение» (по умолчанию SET TRANSACTION READ ONLY по стандарту).
get_begin_sql(), get_commit_sql(), get_rollback_sql() Команды, которые открывают, фиксируют и откатывают транзакцию (по умолчанию START TRANSACTION, COMMIT, ROLLBACK по стандарту), — ими обрамляется SQL, который sqlmigrate печатает для атомарной миграции, и они отправляются там, где hare завершает транзакцию в обход своих обработчиков.
get_statement_timeout_sql(milliseconds), get_lock_timeout_sql(milliseconds) Ограничения времени команды и ожидания блокировки на время транзакции; None, если у базы их нет.

Часть TableLocks редактора схемы (table_locks_class): get_lock_table_sql() блокирует таблицу на время транзакции, а get_migration_lock_sql() выстраивает одновременные запуски migrate по очереди; build_two_phase_commit() даёт Transactions.distributed() его команды.