До сих пор нам встречался только код, работающий с одной сущностью за раз. В тех же примерах, когда создавались или изменялись несколько сущностей одновременно, никак не учитывалась ситуация, при которой посреди процесса обработки возникает ошибка и часть данных успевает перейти в новое состояние, а оставшиеся сущности остаются в старом. В таких случаях говорят: "нарушена консистентность".
В курсе по основам реляционных баз данных вы уже встречались с понятием транзакций и набором требований к транзакционным системам под названием ACID. Большинство СУБД, с которыми Django умеет работать, этим требованиям в той или иной форме соответствуют, а Django ORM предоставляет средства для управления транзакциями.
Тут стоит сразу отметить, что по умолчанию ORM использует режим "autocommit" и все запросы сразу применяются к БД. Об этой особенности стоит помнить и явно использовать транзакции в тех местах, где консистентность может быть нарушена.
Использование atomic
Для того, чтобы пометить фрагмент кода, как относящийся к одной транзакции, обычно используют менеджер контекста atomic():
from django.db import transaction
with transaction.atomic():
# весь код в этом блоке выполнится в рамках транзакции
do_more_stuff()
Умение менеджера контекста реагировать на ошибки, возникающие в его теле, здесь приходится весьма кстати. В результате при первой же ошибке транзакция отменяется.
Разработчики на Django часто хотят выполнить в транзакции весь код какой-либо view
. При этом atomic()
используют как декоратор:
from django.db import transaction
@transaction.atomic
def viewfunc(request):
# Вся view работает в рамках транзакции
do_stuff()
В модуле django.db.transaction
присутствуют две функции: commit()
и rollback()
. Вызов первой применяет накопленные в рамках транзакции изменения к базе данных. Вызов второй отбрасывает эти изменения, как будто транзакции и вовсе не было. Когда atomic()
ловит ошибки, выполняется rollback()
, а если же выполнение обёрнутого кода завершилось успешно, то делается commit()
. Поэтому думать об использовании этих функций обычно не приходится. Полезны эти функции могут быть тогда, когда в рамках блока менеджера контекста без возникновения ошибки становится понятно, что транзакция должна быть завершена так или иначе.
Точки сохранения
Параллельно с транзакциями существуют ещё и точки сохранения (savepoints). Они обычно используются в рамках транзакции и отмечают места, в которых текущее состояние считается консистентным. После создания точки сохранения, к запомненному там состоянию можно вернуться и отбросить таким образом изменения, произошедшие после создания savepoint. Точки сохранения могут быть созданы внутри транзакции и откат к некоторой savepoint не приводит к откату всей транзакции. Можно воспринимать savepoints как некий аналог Undo в редакторе, тогда как транзакция скорее похожа на сохранение всего файла или выход из редактора без сохранения.
Функция savepoint()
из модуля django.db.transaction
создает точку сохранения, возвращая её идентификатор ("savepoint id" или "sid").
Функция savepoint_commit(sid)
сохраняет изменения, произошедшие с момента создания соответствующей savepoint. А функция savepoint_rollback(sid)
откатывает изменения к тому состоянию, которые имела база на момент создания соответствующей savepoint. Тот факт, что при вызове упомянутых двух функций указывается "sid", говорит о том, что откатывать изменения можно к любой из ранее созданных точек сохранения (в пределах транзакции).
Вложенные транзакции
Если внутри кода, уже обёрнутого в вызов atomic()
, в том или ином виде будет использован ещё один вызов atomic()
, то ORM создаст точку сохранения, вместо ещё одной транзакции. Откат таких "вложенных транзакций" не откатывает внешнюю транзакцию.
Ограничения
Транзакции поддерживаются абсолютным большинством СУБД, среди тех, с которыми Django умеет работать. Но этого же нельзя сказать о savepoints. Если конкретная СУБД в принципе не поддерживает точки сохранения, то любой код, который их использует, будет сразу вносить изменения в БД, а откат работать перестанет. И если при явном использовании savepoint()
и прочих специфичных функций программист обычно знает о том, что делает, то использование вложенных вызовов atomic()
может неприятно удивить, если забыть об отсутствии savepoints в конкретной СУБД.
Из сказанного выше стоит сделать следующий вывод: один уровень транзакций работает везде и всегда (практически), а все виды вложенности требуют внимательного отслеживания того, с какими СУБД будет работать проект.
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.