Транзакции базы данных
Django предоставляет несколько способов управления транзакциями базы данных.
Управление транзакциями базы данных
Поведение транзакций Django по умолчанию
По умолчанию Django работает в режиме автоматической фиксации. Каждый запрос немедленно фиксируется в базе данных, если только транзакция не активна. Подробнее см. ниже.
Django автоматически использует транзакции или точки сохранения, чтобы гарантировать целостность операций ORM, требующих выполнения нескольких запросов, особенно запросов delete() и update().
Класс TestCase Django также оборачивает каждый тест в транзакцию из соображений производительности.
Связывание транзакций с HTTP-запросами
Распространённый способ обработки транзакций в веб-приложениях — оборачивать каждый запрос в транзакцию. Установите значение True для параметра ATOMIC_REQUESTS в конфигурации каждой базы данных, для которой нужно включить это поведение.
Это работает следующим образом. Перед вызовом функции-представления Django начинает транзакцию. Если ответ сформирован без проблем, Django фиксирует транзакцию. Если представление вызывает исключение, Django откатывает транзакцию.
В коде представления можно выполнять подчинённые транзакции с помощью точек сохранения, обычно используя менеджер контекста atomic(). Однако при завершении представления будут зафиксированы либо все изменения, либо ни одно из них.
Предупреждение
Хотя простота этой модели транзакций привлекательна, при увеличении нагрузки она также становится неэффективной. Открытие транзакции для каждого представления сопряжено с некоторыми накладными расходами. Влияние на производительность зависит от характера запросов вашего приложения и от того, насколько хорошо ваша база данных справляется с блокировками.
Транзакции на каждый запрос и потоковые ответы
Когда представление возвращает StreamingHttpResponse, чтение содержимого ответа часто приводит к выполнению кода, который формирует это содержимое. Поскольку представление уже вернуло результат, такой код выполняется вне транзакции.
Как правило, не рекомендуется записывать данные в базу при формировании потокового ответа, поскольку после начала отправки ответа невозможно разумным образом обработать ошибки.
На практике эта возможность оборачивает каждую функцию-представление в декоратор atomic(), описанный ниже.
Обратите внимание: транзакцией охвачено только выполнение представления. Промежуточное ПО выполняется вне транзакции, как и рендеринг шаблонных ответов.
Когда параметр 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, durable=False)[исходный код] -
Атомарность — определяющее свойство транзакций базы данных.
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: ...Это также относится к любым другим механизмам, которые могут хранить состояние приложения, например кэшированию или глобальным переменным. Например, если после сохранения объекта код заранее обновляет данные в кэше, рекомендуется вместо этого использовать transaction.on_commit(), чтобы отложить изменение кэша до фактической фиксации транзакции.
Для гарантии атомарности
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 версии 2.0, требует изначально отключать автоматическую фиксацию. Django переопределяет это значение по умолчанию и включает автоматическую фиксацию.
Чтобы этого избежать, можно отключить управление транзакциями, однако это не рекомендуется.
Отключение управления транзакциями
Можно полностью отключить управление транзакциями Django для конкретной базы данных, задав для параметра AUTOCOMMIT значение False в её конфигурации. В этом случае Django не будет включать автоматическую фиксацию и выполнять фиксацию транзакций. Поведение будет обычным для используемой библиотеки базы данных.
Вам потребуется явно фиксировать каждую транзакцию, в том числе начатую Django или сторонними библиотеками. Поэтому этот режим лучше использовать в случаях, когда вы хотите создать собственное промежуточное ПО для управления транзакциями или выполнить что-то действительно необычное.
Выполнение действий после фиксации
Иногда требуется выполнить действие, связанное с текущей транзакцией базы данных, но только если транзакция будет успешно зафиксирована. Например, это может быть фоновая задача, уведомление по электронной почте или инвалидация кэша.
on_commit() позволяет зарегистрировать обратные вызовы, которые будут выполнены после успешной фиксации открытой транзакции:
-
on_commit(func, using=None, robust=False)[исходный код]
Передайте в on_commit() функцию или любой другой вызываемый объект:
from django.db import transaction def send_welcome_email(): ... transaction.on_commit(send_welcome_email)
Обратным вызовам не передаются аргументы, но их можно связать с помощью functools.partial():
from functools import partial
for user in users:
transaction.on_commit(partial(send_invite_email, user=user))
Обратные вызовы вызываются после успешной фиксации открытой транзакции. Если транзакция откатывается (обычно при возникновении необработанного исключения в блоке atomic()), обратный вызов отбрасывается и никогда не вызывается.
Если вызвать on_commit(), когда открытой транзакции нет, обратный вызов будет выполнен немедленно.
Иногда полезно регистрировать обратные вызовы, которые могут завершиться с ошибкой. Передача robust=True позволяет выполнить следующие обратные вызовы, даже если текущий вызовет исключение. Все ошибки, производные от класса Exception Python, перехватываются и записываются в журнал django.db.backends.base.
Для тестирования обратных вызовов, зарегистрированных с помощью on_commit(), можно использовать TestCase.captureOnCommitCallbacks().
Точки сохранения
Точки сохранения (то есть вложенные блоки 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()
Порядок выполнения
Функции, выполняемые после фиксации для заданной транзакции, вызываются в порядке их регистрации.
Обработка исключений
Если одна из функций, зарегистрированных с помощью robust=False в рамках заданной транзакции, вызовет необработанное исключение, остальные зарегистрированные для этой транзакции функции выполнены не будут. Это аналогично последовательному выполнению функций без использования on_commit().
Время выполнения
Обратные вызовы выполняются после успешной фиксации, поэтому ошибка в обратном вызове не приведёт к откату транзакции. Они выполняются только при успешном завершении транзакции, но не являются её частью. Для предполагаемых сценариев использования (уведомления по электронной почте, фоновые задачи и т. д.) этого должно быть достаточно. Если это не так (если последующее действие настолько важно, что его сбой должен приводить к сбою самой транзакции), использовать хук 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.
Почему нет хука отката?
Надёжно реализовать хук отката сложнее, чем хук фиксации, поскольку неявный откат может произойти по разным причинам.
Например, если подключение к базе данных будет потеряно из-за завершения процесса без возможности корректно остановиться, хук отката никогда не выполнится.
Но есть решение: вместо того чтобы что-то делать внутри атомарного блока (транзакции), а затем отменять это при сбое транзакции, используйте on_commit(), чтобы отложить это действие до успешного завершения транзакции. Гораздо проще отменить то, чего вы изначально не делали!
Низкоуровневые API
Предупреждение
По возможности всегда отдавайте предпочтение atomic(). Этот API учитывает особенности каждой базы данных и предотвращает недопустимые операции.
Низкоуровневые API полезны только в том случае, если вы реализуете собственное управление транзакциями.
Автокоммит
Django предоставляет в модуле django.db.transaction API для управления состоянием автокоммита каждого подключения к базе данных.
-
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 неявно фиксирует транзакцию перед операторами работы с точками сохранения. (Фактически фиксация выполняется перед любым оператором, кроме SELECT, INSERT, UPDATE, DELETE и REPLACE.) У этой ошибки есть два последствия:
Транзакции в 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/6.0/topics/db/transactions/