Базы данных транзакции
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)[source] -
Этот декоратор аннулирует действие
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)[source] -
Атомарность — определяющее свойство транзакций базы данных.
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, спецификация Python Database API v2.0, требует, чтобы автокоммит был выключен по умолчанию. Django переопределяет это значение по умолчанию и включает автокоммит.
Чтобы этого избежать, вы можете отключить управление транзакциями, но это не рекомендуется.
Отключение управления транзакциями
Вы можете полностью отключить управление транзакциями Django для заданной базы данных, установив AUTOCOMMIT на False в её конфигурации. Если вы сделаете это, Django не включит автокоммит и не будет выполнять никаких коммитов. Вы получите стандартное поведение библиотеки подлежащей базы данных.
Это требует от вас явного коммита каждой транзакции, даже тех, которые были инициированы Django или сторонними библиотеками. Поэтому это лучше использовать в ситуациях, когда вы хотите запустить свой собственный middleware для управления транзакциями или сделать что-то действительно необычное.
Выполнение действий после коммита
Иногда вам нужно выполнить действие, связанное с текущей базой данных транзакции, но только в случае успешного коммита транзакции. Примеры могут включать задачу Celery, уведомление по электронной почте или обновление кэша.
Django предоставляет функцию on_commit() для регистрации функций обратного вызова, которые должны быть выполнены после успешного коммита транзакции:
-
on_commit(func, using=None)[source]
Передайте любую функцию (которая не принимает аргументов) в 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 http://initd.org/psycopg/docs/usage.html#tpc и необязательные расширения двухфазного коммита в спецификации Python DB-API https://www.python.org/dev/peps/pep-0249/#optional-two-phase-commit-extensions.
Обратные вызовы не выполняются до тех пор, пока режим автокоммита не будет восстановлен на подключении после коммита (иначе любые запросы, выполняемые в обратном вызове, откроют неявную транзакцию, что помешает подключению вернуться в режим автокоммита).
В режиме автокоммита и вне блока atomic() функция будет выполнена немедленно, а не при коммите.
Функции обратного вызова при коммите работают только с режимом автокоммита и API транзакций atomic() (или ATOMIC_REQUESTS). Вызов on_commit() при отключенном автокоммите и без нахождения внутри блока атомарных операций приведёт к ошибке.
Использование в тестах
Класс TestCase Django оборачивает каждый тест в транзакцию и откатывает эту транзакцию после каждого теста, чтобы обеспечить изоляцию тестов. Это означает, что ни одна транзакция фактически не коммитится, поэтому ваши функции обратного вызова on_commit() никогда не будут выполнены. Если вам нужно проверить результат функции обратного вызова on_commit(), используйте вместо этого TransactionTestCase.
Почему нет функции обратного вызова при откате?
Функцию обратного вызова при откате сложнее реализовать надёжно, чем функцию обратного вызова при коммите, так как существует множество различных причин для неявного отката.
Например, если подключение к базе данных прерывается, потому что ваш процесс был завершён без возможности корректного завершения, ваша функция обратного вызова при откате никогда не будет выполнена.
Решение простое: вместо того, чтобы делать что-то внутри атомарного блока (транзакции), а затем отменять это, если транзакция терпит неудачу, используйте on_commit(), чтобы отложить выполнение до тех пор, пока транзакция не будет успешной. Гораздо проще отменить то, что вы никогда не делали в первую очередь!
API низкого уровня
Предупреждение
Всякий раз, когда это возможно, предпочтительнее использовать atomic(). Она учитывает особенности каждой базы данных и предотвращает недействительные операции.
API низкого уровня полезен только при реализации собственного управления транзакциями.
Автокоммит
Django предоставляет простой API в модуле django.db.transaction для управления состоянием автокоммита каждого подключения к базе данных.
-
get_autocommit(using=None)[source]
-
set_autocommit(autocommit, using=None)[source]
Эти функции принимают аргумент using, который должен быть именем базы данных. Если он не указан, Django использует базу данных "default".
Автокоммит изначально включен. Если вы его отключите, вам придётся его восстановить.
После отключения автокоммита вы получаете стандартное поведение вашего адаптера базы данных, и Django вам не поможет. Хотя это поведение указано в PEP 249, реализации адаптеров не всегда согласованы друг с другом. Тщательно изучите документацию используемого вами адаптера.
Вы должны убедиться, что транзакция не активна, обычно выполнив commit() или rollback(), прежде чем включать автокоммит.
Django откажется отключить автокоммит, когда активен блок atomic(), поскольку это нарушит атомарность.
Транзакции
Транзакция — это атомарный набор запросов к базе данных. Даже если ваша программа аварийно завершит работу, база данных гарантирует, что либо все изменения будут применены, либо ни одно из них.
Django не предоставляет API для начала транзакции. Ожидаемый способ начать транзакцию — отключить автозапись с помощью set_autocommit().
После того, как вы находитесь в транзакции, вы можете либо применить изменения, которые вы выполнили до этого момента, с помощью commit(), либо отменить их с помощью rollback(). Эти функции определены в django.db.transaction.
-
commit(using=None)[source]
-
rollback(using=None)[source]
Эти функции принимают аргумент 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)[source] -
Создаёт новую сохраняемую точку. Это отмечает точку в транзакции, которая, как известно, находится в «хорошем» состоянии. Возвращает идентификатор сохраняемой точки (
sid).
-
savepoint_commit(sid, using=None)[source] -
Освобождает сохраняемую точку
sid. Изменения, выполненные с момента создания сохраняемой точки, становятся частью транзакции.
-
savepoint_rollback(sid, using=None)[source] -
Откатывает транзакцию до сохраняемой точки
sid.
Эти функции ничего не делают, если сохраняемые точки не поддерживаются или если база данных находится в режиме автозаписи.
Кроме того, есть вспомогательная функция:
-
clean_savepoints(using=None)[source] -
Сбрасывает счётчик, используемый для генерации уникальных идентификаторов сохраняемых точек.
Следующий пример демонстрирует использование сохраняемых точек:
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)[source]
-
set_rollback(rollback, using=None)[source]
Установка флага отката на True принудительно вызывает откат при выходе из самого внутреннего блока atomic. Это может быть полезно для запуска отката без поднятия исключения.
Установка его на False предотвращает такой откат. Прежде чем сделать это, убедитесь, что вы откатили транзакцию до известной хорошей сохраняемой точки внутри текущего блока atomic! В противном случае вы нарушите атомарность, и могут возникнуть проблемы с целостностью данных.
Примечания, специфичные для базы данных
Сохраняемые точки в SQLite
Хотя SQLite поддерживает сохраняемые точки, недостаток в дизайне модуля sqlite3 делает их почти непригодными для использования.
Когда автозапись включена, сохраняемые точки не имеют смысла. Когда она отключена, sqlite3 неявно подтверждает перед операциями сохраняемых точек. (Фактически, он подтверждает перед любой операцией, кроме 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/2.2/topics/db/transactions/