Базы данных транзакции
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, durable=False)[source] -
Атомарность — определяющее свойство транзакций базы данных.
atomicпозволяет создавать блок кода, в котором гарантируется атомарность базы данных. Если блок кода успешно завершен, изменения подтверждаются в базе данных. Если возникает исключение, изменения откатываются.Блоки
atomicмогут быть вложены. В этом случае, когда внутренний блок завершается успешно, его результаты всё ещё могут быть отменены, если в наружном блоке позже возникает исключение.Иногда бывает полезно гарантировать, что блок
atomicвсегда является внешним блокомatomic, обеспечивая подтверждение любых изменений в базе данных при выходе из блока без ошибок. Это известно как устойчивость и может быть достигнуто установкойdurable=True. Если блокatomicвложен в другой, он вызывает исключениеRuntimeError.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.test.TestCase.
Автозавершение транзакций
Почему Django использует автозавершение
В стандартах SQL каждый запрос SQL запускает транзакцию, если она не активна. Такие транзакции должны быть явно подтверждены или отменены.
Это не всегда удобно для разработчиков приложений. Для решения этой проблемы большинство баз данных предоставляют режим автозавершения. Когда автозавершение включено и нет активной транзакции, каждый запрос SQL выполняется в рамках своей собственной транзакции. Другими словами, каждый такой запрос не только запускает транзакцию, но и транзакция автоматически подтверждается или отменяется в зависимости от успешности запроса.
PEP 249, спецификация Python Database API v2.0, требует, чтобы автозавершение было изначально выключено. Django переопределяет это значение по умолчанию и включает автозавершение.
Чтобы избежать этого, вы можете отключить управление транзакциями, но это не рекомендуется.
Отключение управления транзакциями
Вы можете полностью отключить управление транзакциями Django для конкретной базы данных, установив AUTOCOMMIT в False в ее конфигурации. Если вы сделаете это, Django не включит автозавершение и не выполнит никаких подтверждений. Вы получите обычное поведение библиотеки баз данных.
Это требует явного подтверждения каждой транзакции, даже тех, которые были инициированы Django или сторонними библиотеками. Следовательно, это лучше всего использовать в ситуациях, когда вы хотите запустить собственное ПО для управления транзакциями или выполнить что-то действительно необычное.
Выполнение действий после подтверждения
Иногда вам нужно выполнить действие, связанное с текущей базой данных, но только если транзакция успешно подтверждена. Примеры включают задачу 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 и опциональные расширения двухфазного подтверждения в спецификации Python DB-API.
Функции обратного вызова не выполняются до тех пор, пока режим автозавершения не будет восстановлен на подключении после подтверждения (иначе любые запросы, выполненные в функции обратного вызова, откроют неявную транзакцию, что предотвратит возврат соединения в режим автозавершения).
В режиме автозавершения и вне блока atomic() функция будет выполнена сразу, а не при подтверждении.
Функции обратного вызова при подтверждении работают только с режимом автозавершения и API транзакций atomic() (или ATOMIC_REQUESTS). Вызов on_commit() при отключенном автозавершении и вне блока атомарной операции приведет к ошибке.
Использование в тестах
Класс TestCase Django оборачивает каждый тест в транзакцию и отменяет эту транзакцию после каждого теста, чтобы обеспечить изоляцию тестов. Это означает, что транзакция никогда фактически не подтверждается, поэтому ваши функции обратного вызова on_commit() никогда не будут выполнены.
Вы можете преодолеть это ограничение, используя TestCase.captureOnCommitCallbacks(). Это позволяет захватить ваши функции обратного вызова on_commit() в список, что позволяет делать утверждения о них или эмулировать подтверждение транзакции, вызывая их.
Другой способ преодолеть ограничение — использовать TransactionTestCase вместо TestCase. Это означает, что ваши транзакции будут подтверждаться, и функции обратного вызова будут выполняться. Однако TransactionTestCase очищает базу данных между тестами, что значительно медленнее, чем изоляция TestCase.
Почему нет функции обратного вызова отмены?
Функцию обратного вызова отмены сложнее реализовать надежно, чем функцию обратного вызова подтверждения, поскольку существует множество причин для неявной отмены.
Например, если соединение с базой данных прервано, потому что ваш процесс был завершен без возможности корректного завершения, функция обратного вызова отмены никогда не будет выполнена.
Но есть решение: вместо выполнения чего-либо в блоке atomic (транзакция), а затем отмены этого действия, если транзакция завершится неудачно, используйте on_commit(), чтобы отложить выполнение до подтверждения транзакции. Гораздо легче отменить то, что вы никогда не выполняли!
API низкого уровня
Предупреждение
Всегда отдавайте предпочтение atomic(), если это возможно. Он учитывает особенности каждой базы данных и предотвращает недопустимые операции.
API низкого уровня полезны только при реализации собственного управления транзакциями.
Автозавершение
Django предоставляет API в модуле django.db.transaction для управления состоянием автозавершения каждого подключения к базе данных.
-
get_autocommit(using=None)[source]
-
set_autocommit(autocommit, using=None)[source]
Эти функции принимают аргумент using, который должен быть именем базы данных. Если он не указан, Django использует базу данных "default".
Автозапись (autocommit) по умолчанию включена. Если вы её выключаете, вы несете ответственность за её восстановление.
После отключения автозаписи вы получаете поведение по умолчанию вашего адаптера базы данных, и 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 предотвращает такой откат. Перед этим убедитесь, что вы откатили транзакцию до известной хорошей сохраняющей точки внутри текущего атомарного блока! В противном случае вы нарушаете атомарность, и может произойти повреждение данных.
Примечания, специфичные для базы данных
Сохраняющие точки в 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/4.1/topics/db/transactions/