Базы данных транзакции
Django предоставляет несколько способов управления транзакциями базы данных.
Управление транзакциями базы данных
Поведение транзакций по умолчанию Django
По умолчанию Django работает в режиме автоподтверждения. Каждый запрос сразу же подтверждается в базе данных, если транзакция не активна. Подробнее см. ниже.
Django автоматически использует транзакции или точки сохранения для обеспечения целостности операций ORM, которые требуют нескольких запросов, особенно запросов delete() и update().
Класс Django TestCase также оборачивает каждый тест в транзакцию по соображениям производительности.
Связывание транзакций с HTTP-запросами
Распространённый способ обработки транзакций в веб-приложениях — это оборачивание каждого запроса в транзакцию. Установите ATOMIC_REQUESTS в значение True в настройках каждой базы данных, для которой вы хотите включить это поведение.
Это работает следующим образом. Перед вызовом функции представления Django запускает транзакцию. Если ответ генерируется без проблем, Django подтверждает транзакцию. Если представление вызывает исключение, Django откатывает транзакцию.
Вы можете выполнять подтранзакции с помощью точек сохранения в коде представления, обычно с помощью менеджера контекста atomic(). Однако в конце представления все изменения будут подтверждены или же ни одно не будет.
Предупреждение
Хотя простота этой модели транзакций привлекательна, она также делает её неэффективной при увеличении трафика. Открытие транзакции для каждого представления создаёт некоторую нагрузку. Влияние на производительность зависит от шаблонов запросов вашего приложения и от того, как ваша база данных обрабатывает блокировки.
Транзакции на запрос и потоковые ответы
Когда представление возвращает StreamingHttpResponse, чтение содержимого ответа часто выполняет код для генерации содержимого. Поскольку представление уже вернулось, такой код выполняется вне транзакции.
В целом, не рекомендуется записывать в базу данных во время генерации потокового ответа, так как нет разумного способа обработки ошибок после начала отправки ответа.
На практике эта функция оборачивает каждую функцию представления в декоратор atomic(), описанный ниже.
Обратите внимание, что только выполнение вашего представления находится внутри транзакции. Middleware выполняется вне транзакции, а также рендеринг ответов шаблонов.
Когда ATOMIC_REQUESTS включен, всё ещё можно предотвратить выполнение представлений в транзакции.
-
non_atomic_requests(using=None) -
Этот декоратор аннулирует действие
ATOMIC_REQUESTSдля данного представления:from django.db import transaction @transaction.non_atomic_requests def my_view(request): do_stuff() @transaction.non_atomic_requests(using='other') def my_other_view(request): do_stuff_on_the_other_database()Он работает только при применении к самому представлению.
Явное управление транзакциями
Django предоставляет единый API для управления транзакциями базы данных.
-
atomic(using=None, savepoint=True) -
Атомарность — определяющее свойство транзакций базы данных.
atomicпозволяет нам создать блок кода, в котором гарантируется атомарность в базе данных. Если блок кода успешно завершен, изменения подтверждаются в базе данных. Если произошла ошибка, изменения отменяются.Блоки
atomicмогут быть вложены. В этом случае, когда внутренний блок завершается успешно, его эффекты всё ещё могут быть отменены, если в наружном блоке позже произойдёт исключение.atomicможет использоваться как декоратор:from django.db import transaction @transaction.atomic def viewfunc(request): # This code executes inside a transaction. do_stuff()и как менеджер контекста:
from django.db import transaction def viewfunc(request): # This code executes in autocommit mode (Django's default). do_stuff() with transaction.atomic(): # This code executes inside a transaction. do_more_stuff()Оборачивание
atomicв блок try/except позволяет естественным образом обрабатывать ошибки целостности:from django.db import IntegrityError, transaction @transaction.atomic def viewfunc(request): create_parent() try: with transaction.atomic(): generate_relationships() except IntegrityError: handle_exception() add_children()В этом примере, даже если
generate_relationships()вызывает ошибку базы данных, нарушая ограничение целостности, вы можете выполнить запросы вadd_children(), и изменения отcreate_parent()всё ещё будут там и связаны с той же транзакцией. Обратите внимание, что любые операции, предпринятые вgenerate_relationships(), уже будут безопасно отменены, когдаhandle_exception()будет вызван, поэтому обработчик исключений также может работать с базой данных при необходимости.Избегайте перехвата исключений внутри
atomic!При выходе из блока
atomicDjango проверяет, был ли он покинут нормально или с исключением, чтобы определить, подтвердить или отменить. Если вы перехватываете и обрабатываете исключения внутри блокаatomic, вы можете скрыть от Django тот факт, что произошла проблема. Это может привести к непредсказуемому поведению.Это в основном относится к
DatabaseErrorи его подклассам, таким какIntegrityError. После такой ошибки транзакция прерывается, и Django выполнит откат в конце блокаatomic. Если вы попытаетесь выполнить запросы к базе данных до того, как произойдёт откат, Django выведетTransactionManagementError. Вы также можете столкнуться с таким поведением, когда обработчик сигнала ORM вызывает исключение.Правильный способ перехвата ошибок базы данных — вокруг блока
atomic, как показано выше. При необходимости добавьте дополнительный блокatomicдля этой цели. Этот шаблон обладает ещё одним преимуществом: он чётко определяет, какие операции будут отменены, если произойдёт исключение.Если вы перехватываете исключения, вызванные сырыми SQL-запросами, поведение Django не определено и зависит от базы данных.
Возможно, вам придётся вручную восстановить состояние модели при откате транзакции.
Значения полей модели не будут восстановлены при откате транзакции. Это может привести к несогласованному состоянию модели, если вы не восстановите исходные значения полей вручную.
Например, учитывая
MyModelс полемactive, этот фрагмент кода гарантирует, что проверкаif obj.activeв конце использует правильное значение, если обновлениеactiveнаTrueв транзакции не удалось:from django.db import DatabaseError, transaction obj = MyModel(active=False) obj.active = True try: with transaction.atomic(): obj.save() except DatabaseError: obj.active = False if obj.active: ...Для обеспечения атомарности
atomicотключает некоторые API. Попытка подтвердить, отменить или изменить состояние автоподтверждения подключения к базе данных внутри блокаatomicвызовет исключение.atomicпринимает аргументusing, который должен быть именем базы данных. Если этот аргумент не указан, Django использует базу данных"default".Внутри Django транзакционного управления кода:
- открывается транзакция при входе в самый внешний блок
atomic; - создаётся точка сохранения при входе во внутренний блок
atomic; - освобождается или отменяется до точки сохранения при выходе из внутреннего блока;
- подтверждается или отменяется транзакция при выходе из внешнего блока.
Вы можете отключить создание точек сохранения для внутренних блоков, установив аргумент
savepointв значениеFalse. Если произойдёт исключение, Django выполнит откат при выходе из первого родительского блока с точкой сохранения, если она есть, или из внешнего блока в противном случае. Атомарность всё ещё гарантируется внешней транзакцией. Этот параметр следует использовать только в том случае, если накладные расходы точек сохранения заметны. У него есть недостаток — он нарушает обработку ошибок, описанную выше.Вы можете использовать
atomicпри выключенном автоподтверждении. Он будет использовать только точки сохранения, даже для самого внешнего блока. - открывается транзакция при входе в самый внешний блок
Учёт производительности
Открытые транзакции оказывают влияние на производительность сервера базы данных. Чтобы минимизировать эти накладные расходы, старайтесь держать транзакции как можно короче. Это особенно важно, если вы используете atomic() в долговременных процессах за пределами цикла запроса/ответа Django.
Автоподтверждение
Зачем Django использует автоподтверждение
В стандартах SQL каждый SQL-запрос запускает транзакцию, если не активна уже какая-либо. Такие транзакции должны быть явно подтверждены или отменены.
Это не всегда удобно для разработчиков приложений. Чтобы решить эту проблему, большинство баз данных предоставляют режим автоподтверждения. Когда автоподтверждение включено и нет активной транзакции, каждый SQL-запрос оборачивается в свою собственную транзакцию. Другими словами, каждый такой запрос не только запускает транзакцию, но и транзакция автоматически подтверждается или отменяется в зависимости от успешности запроса.
PEP 249, спецификация API баз данных Python версии 2.0, требует, чтобы автоподтверждение было изначально выключено. Django переопределяет это значение по умолчанию и включает автоподтверждение.
Для предотвращения этого, вы можете отключить управление транзакциями, но это не рекомендуется.
Отключение управления транзакциями
Вы можете полностью отключить управление транзакциями Django для определённой базы данных, установив AUTOCOMMIT в значение False в её конфигурации. Если вы это сделаете, Django не включит автоматическое подтверждение и не выполнит никаких подтверждений. Вы получите обычное поведение библиотеки базовой базы данных.
Это требует явного подтверждения каждой транзакции, даже тех, которые начаты Django или сторонними библиотеками. Поэтому это лучше всего использовать в ситуациях, когда вы хотите запустить собственный middleware для управления транзакциями или сделать что-то действительно необычное.
Выполнение действий после подтверждения
Иногда вам нужно выполнить действие, связанное с текущей базой данных транзакции, но только если транзакция успешно подтверждена. Примерами могут служить задача Celery, уведомление по электронной почте или очистка кэша.
Django предоставляет функцию on_commit() для регистрации функций обратного вызова, которые должны выполняться после успешного подтверждения транзакции:
-
on_commit(func, using=None)
Передайте любую функцию (не принимающую аргументы) в on_commit():
from django.db import transaction
def do_something():
pass # send a mail, invalidate a cache, fire off a Celery task, etc.
transaction.on_commit(do_something)
Вы также можете обернуть свою функцию в лямбда-выражение:
transaction.on_commit(lambda: some_celery_task.delay('arg1'))
Функция, которую вы передаёте, будет вызвана сразу после того, как гипотетическая запись в базе данных, сделанная там, где вызов on_commit() был успешно подтверждена.
Если вы вызовете on_commit(), когда активной транзакции нет, обратный вызов будет выполнен немедленно.
Если эта гипотетическая запись в базе данных будет вместо этого отменена (обычно, когда возникает необработанное исключение в блоке atomic()), ваша функция будет отброшена и никогда не будет вызвана.
Точки сохранения
Точки сохранения (т.е. вложенные блоки atomic()) обрабатываются правильно. То есть, вызываемый объект on_commit(), зарегистрированный после точки сохранения (во вложенном блоке atomic()), будет вызван после подтверждения внешней транзакции, но не если произошла отмена к этой точке сохранения или любой предыдущей точке сохранения во время транзакции:
with transaction.atomic(): # Outer atomic, start a new transaction
transaction.on_commit(foo)
with transaction.atomic(): # Inner atomic block, create a savepoint
transaction.on_commit(bar)
# foo() and then bar() will be called when leaving the outermost block
С другой стороны, когда точка сохранения отменяется (из-за возникновения исключения), внутренний вызываемый объект не будет вызван:
with transaction.atomic(): # Outer atomic, start a new transaction
transaction.on_commit(foo)
try:
with transaction.atomic(): # Inner atomic block, create a savepoint
transaction.on_commit(bar)
raise SomeError() # Raising an exception - abort the savepoint
except SomeError:
pass
# foo() will be called, but not bar()
Порядок выполнения
Функции обратного вызова для данной транзакции выполняются в том порядке, в котором они были зарегистрированы.
Обработка исключений
Если одна функция обратного вызова внутри данной транзакции вызывает необработанное исключение, последующие зарегистрированные функции в той же транзакции не будут выполнены. Это то же поведение, что и если бы вы выполняли функции по порядку сами без on_commit().
Время выполнения
Ваши обратные вызовы выполняются после успешного подтверждения, поэтому ошибка в обратном вызове не приведёт к откату транзакции. Они выполняются условно при успешном выполнении транзакции, но они не являются частью транзакции. Для преднамеренных случаев использования (уведомления по почте, задачи Celery и т.д.) это должно быть нормально. Если это не так (если ваше последующее действие настолько важно, что его ошибка должна означать ошибку всей транзакции), то вы не хотите использовать крючок on_commit(). Вместо этого вы можете использовать двухфазный протокол подтверждения, например, двухфазный протокол подтверждения, такой как поддержка протокола двухфазного подтверждения psycopg двухфазного подтверждения и опциональные расширения двухфазного подтверждения в спецификации Python DB-API.
Обратные вызовы не выполняются до тех пор, пока в соединении после подтверждения не будет восстановлено автоматическое подтверждение (поскольку в противном случае любые запросы, выполненные в обратном вызове, откроют неявную транзакцию, не позволяя соединению вернуться в режим автоматического подтверждения).
При работе в режиме автоматического подтверждения и вне блока atomic() функция будет выполнена немедленно, а не при подтверждении.
Функции обратного вызова при подтверждении работают только с режимом автоматического подтверждения и API транзакций atomic() (или ATOMIC_REQUESTS). Вызов on_commit() при отключённом автоматическом подтверждении и вне блока atomic приведёт к ошибке.
Использование в тестах
Класс TestCase Django обертывает каждый тест в транзакцию и отменяет эту транзакцию после каждого теста, чтобы обеспечить изоляцию тестов. Это означает, что транзакция никогда фактически не подтверждается, поэтому ваши обратные вызовы on_commit() никогда не будут выполнены. Если вам нужно проверить результаты обратного вызова on_commit(), используйте вместо этого класс TransactionTestCase.
Почему нет крючка отката?
Крючок отката сложнее реализовать надёжно, чем крючок подтверждения, поскольку существует множество вещей, которые могут вызвать неявный откат.
Например, если соединение с вашей базой данных обрывается, потому что ваш процесс был убит без возможности плавного завершения, ваш крючок отката никогда не сработает.
Но есть решение: вместо того, чтобы делать что-то во время блока atomic (транзакции), а затем отменять это, если транзакция завершится неудачно, используйте on_commit(), чтобы отложить выполнение до тех пор, пока транзакция не завершится успешно. Отменить то, чего вы никогда не делали в первую очередь, намного проще!
API низкого уровня
Предупреждение
В идеале всегда используйте atomic(). Он учитывает особенности каждой базы данных и предотвращает недопустимые операции.
API низкого уровня полезны только в случае реализации собственного управления транзакциями.
Автоподтверждение
Django предоставляет API в модуле django.db.transaction для управления состоянием автоматического подтверждения каждого соединения с базой данных.
-
get_autocommit(using=None)
-
set_autocommit(autocommit, using=None)
Эти функции принимают аргумент using, который должен быть именем базы данных. Если он не предоставлен, Django использует базу данных "default".
Автоматическое подтверждение изначально включено. Если вы его отключите, вам придётся его восстановить.
После отключения автоматического подтверждения вы получите поведение по умолчанию вашего адаптера базы данных, и Django вам не поможет. Хотя это поведение указано в PEP 249, реализации адаптеров не всегда согласованны. Тщательно изучите документацию используемого вами адаптера.
Необходимо убедиться, что нет активных транзакций, обычно вызвав commit() или rollback(), перед повторным включением автоматического подтверждения.
Django откажется отключить автоматическое подтверждение, когда активен блок atomic(), потому что это нарушит атомарность.
Транзакции
Транзакция — это атомарный набор запросов к базе данных. Даже если ваша программа аварийно завершит работу, база данных гарантирует, что либо все изменения будут применены, либо ни одного.
Django не предоставляет API для начала транзакции. Ожидаемый способ начала транзакции — отключение автоматического подтверждения с помощью set_autocommit().
После входа в транзакцию вы можете либо применить изменения, выполненные до этого момента, с помощью commit(), либо отменить их с помощью rollback(). Эти функции определены в django.db.transaction.
-
commit(using=None)
-
rollback(using=None)
Эти функции принимают аргумент using, который должен быть именем базы данных. Если он не указан, Django использует базу данных "default".
Django откажется от коммита или отката, когда активен блок atomic(), так как это нарушит атомарность.
Точки сохранения
Точка сохранения — это метка внутри транзакции, которая позволяет откатить часть транзакции, а не всю транзакцию. Точки сохранения доступны с бэкендами SQLite, PostgreSQL, Oracle и MySQL (при использовании хранилища InnoDB). Другие бэкэнды предоставляют функции точек сохранения, но они являются пустыми операциями — они фактически ничего не делают.
Точки сохранения не особенно полезны, если вы используете автоматический коммит, стандартное поведение Django. Однако, как только вы откроете транзакцию с помощью atomic(), вы создаёте серию операций с базой данных, ожидающих коммита или отката. Если вы выполните откат, вся транзакция будет откачена. Точки сохранения предоставляют возможность выполнять откаты с точностью до операции, а не полный откат, который выполнит transaction.rollback().
Когда декоратор atomic() вложен, он создаёт точку сохранения для частичного коммита или отката. Настоятельно рекомендуется использовать atomic(), а не описанные ниже функции, но они по-прежнему являются частью публичного API, и нет планов их устаревать.
Каждая из этих функций принимает аргумент using, который должен быть именем базы данных, на которую распространяется это поведение. Если аргумент using не указан, используется база данных "default".
Точки сохранения управляются тремя функциями в django.db.transaction:
-
savepoint(using=None) -
Создаёт новую точку сохранения. Это отмечает момент в транзакции, который, как известно, находится в «хорошем» состоянии. Возвращает идентификатор точки сохранения (
sid).
-
savepoint_commit(sid, using=None) -
Освобождает точку сохранения
sid. Изменения, внесённые с момента создания точки сохранения, становятся частью транзакции.
-
savepoint_rollback(sid, using=None) -
Откатывает транзакцию до точки сохранения
sid.
Эти функции ничего не делают, если точки сохранения не поддерживаются или если база данных находится в режиме автоматического коммита.
Кроме того, есть вспомогательная функция:
-
clean_savepoints(using=None) -
Сбрасывает счётчик, используемый для генерации уникальных идентификаторов точек сохранения.
Следующий пример демонстрирует использование точек сохранения:
from django.db import transaction
# open a transaction
@transaction.atomic
def viewfunc(request):
a.save()
# transaction now contains a.save()
sid = transaction.savepoint()
b.save()
# transaction now contains a.save() and b.save()
if want_to_keep_b:
transaction.savepoint_commit(sid)
# open transaction still contains a.save() and b.save()
else:
transaction.savepoint_rollback(sid)
# open transaction now contains only a.save()
Точки сохранения могут использоваться для восстановления после ошибки базы данных, выполняя частичный откат. Если вы делаете это внутри блока atomic(), весь блок всё равно будет откачен, потому что он не знает, что вы обработали ситуацию на более низком уровне! Чтобы предотвратить это, вы можете контролировать поведение отката с помощью следующих функций.
-
get_rollback(using=None)
-
set_rollback(rollback, using=None)
Установление флага отката в True заставляет произвести откат при выходе из самого внутреннего атомарного блока. Это может быть полезно для вызова отката без повышения исключения.
Установив его в False , вы предотвращаете такой откат. Перед этим убедитесь, что вы откатили транзакцию до известной хорошей точки сохранения внутри текущего атомарного блока! В противном случае вы нарушите атомарность, и может произойти повреждение данных.
Особенности баз данных
Точки сохранения в SQLite
Хотя SQLite поддерживает точки сохранения, недостаток в дизайне модуля sqlite3 делает их практически непригодными для использования.
Когда включен автоматический коммит, точки сохранения не имеют смысла. Когда он отключён, sqlite3 неявно коммитит перед операциями savepoint statements. (На самом деле, он коммитит перед любой операцией, кроме SELECT, INSERT, UPDATE, DELETE и REPLACE.) Эта ошибка имеет два последствия:
- Низкоуровневые API для точек сохранения применимы только внутри транзакции, т.е. внутри блока
atomic(). - Невозможно использовать
atomic(), когда автоматический коммит выключен.
Транзакции в MySQL
Если вы используете MySQL, ваши таблицы могут или не могут поддерживать транзакции; это зависит от вашей версии MySQL и типов используемых таблиц. (Под «типами таблиц» мы имеем в виду что-то вроде «InnoDB» или «MyISAM».) Особенности транзакций MySQL не входят в сферу этой статьи, но на сайте MySQL есть информация о транзакциях MySQL.
Если ваша настройка MySQL не поддерживает транзакции, Django всегда будет работать в режиме автоматического коммита: операторы будут выполняться и коммититься сразу же после вызова. Если ваша настройка MySQL поддерживает транзакции, Django будет обрабатывать транзакции, как описано в этом документе.
Обработка исключений в транзакциях PostgreSQL
Примечание
Этот раздел актуален только если вы реализуете собственное управление транзакциями. Эта проблема не может возникнуть в стандартном режиме Django и atomic() обрабатывает её автоматически.
Внутри транзакции, когда вызов курсора PostgreSQL вызывает исключение (обычно IntegrityError), все последующие SQL-запросы в той же транзакции завершатся ошибкой «текущая транзакция прервана, запросы игнорируются до конца блока транзакции». Хотя основное использование save() вряд ли вызовет исключение в PostgreSQL, существуют более сложные варианты использования, которые могут, например, сохранение объектов с уникальными полями, сохранение с флагом force_insert/force_update или вызов пользовательского SQL.
Существует несколько способов восстановления после такого рода ошибки.
Откат транзакции
Первый вариант — откатить всю транзакцию. Например:
a.save() # Succeeds, but may be undone by transaction rollback
try:
b.save() # Could throw exception
except IntegrityError:
transaction.rollback()
c.save() # Succeeds, but a.save() may have been undone
Вызов transaction.rollback() откатывает всю транзакцию. Любые неподтверждённые операции с базой данных будут потеряны. В этом примере изменения, внесённые a.save() , будут потеряны, даже если эта операция сама не подняла ошибку.
Откат до точки сохранения
Можно использовать точки сохранения для управления объёмом отката. Перед выполнением операции с базой данных, которая может завершиться ошибкой, можно установить или обновить точку сохранения; таким образом, если операция завершится ошибкой, можно откатить только эту операцию, а не всю транзакцию. Например:
a.save() # Succeeds, and never undone by savepoint rollback
sid = transaction.savepoint()
try:
b.save() # Could throw exception
transaction.savepoint_commit(sid)
except IntegrityError:
transaction.savepoint_rollback(sid)
c.save() # Succeeds, and a.save() is never undone
В этом примере a.save() не будет отменён в случае, если b.save() вызывает исключение.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/3.1/topics/db/transactions/