Django предоставляет несколько инструментов для управления транзакциями базы данных.
По умолчанию Django использует режим автоматической фиксации(“autocommit”). Каждый запрос сразу фиксируется в базе данных, если не используется транзакция. Смотрите ниже.
Django автоматически использует транзакции или точки сохранения для операций ORM, которые требуют нескольких запросов, особенно для delete() и update().
Класс TestCase также оборачивает каждый тест в транзакцию для оптимизации скорости выполнения тестов.
Обычной практикой для Web-приложений является оборачивание каждого запроса в транзакцию. Чтобы активировать такое поведение, установите ATOMIC_REQUESTS в True для соответствующей базы данных.
Это работает следующим образом: при получении запроса Django начинает транзакцию. Если ответ был создан без возникновения ошибок, Django фиксирует все ожидающие транзакции. Если функция представления вызывает исключение, Django откатывает все ожидающие транзакции.
Вы можете выполнять частичную фиксацию или откат изменений в коде представления, обычно с помощью менеджера контекста atomic(). Но в конце выполнения представления будут зафиксированы все изменения, или не одно из них.
Предупреждение
Хотя такая обработка запросов выглядит очень простой и привлекательной, она может быть неэффективна при росте количества запросов. Создание транзакции для каждого запроса создает небольшую нагрузку на базу данных. Влияние на производительность сервера зависит от запросов в вашем приложении и от того, на сколько эффективно ваша база данных работает с блокировками.
Транзакции для запроса и потоковый ответ
Когда представление возвращает StreamingHttpResponse, чтение содержимого ответа выполняет некоторый код для его ответа. Т.к. представление уже отработало, этот код будет выполняться вне транзакции.
Не советуем выполнять запись в базу данных при генерации потокового ответа, т.к. нет хорошего способа обработать ошибки после начала отправки ответа.
На практике каждое представление просто оборачивается в декоратор atomic(), описанный ниже.
Обратите внимание, только представления оборачиваются в транзакцию. Промежуточные слои(middleware) выполняются вне транзакции, аналогично выполняется и рендеринг ответа.
При включенной ATOMIC_REQUESTS все еще существует возможность не использовать транзакцию для представления.
Этот декоратор отключает эффект 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 позволяет создать блок кода, для которого гарантируется атомарность операций над базой данных. Если этот блок кода выполнился без ошибок, все изменения фиксируются в базе данных. Если произошла ошибка, все изменений будут отменены.
atomic блоки могут быть вложенными. В этом случае, если вложенный блок выполнился успешно, изменения в базе данных, которые он произвел, могут быть отменены при ошибке во внешнем блоке кода.
atomic может использоваться как decorator:
from django.db import transaction
@transaction.atomic
def viewfunc(request):
# This code executes inside a transaction.
do_stuff()
и как context manager:
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()
В этом примере, вы можете выполнить запросы в add_children(), даже если generate_relationships() вызывал ошибку, также никуда не денутся изменения, выполненные в create_parent(). Обратите внимание, любые операции из generate_relationships() уже будут отменены, и при обработке ошибки в handle_exception() можно безопасно выполнять запросы к базе данных.
Избегайте перехвата ошибок в atomic!
При выходе из блока atomic Django определяет произошла ли ошибка, чтобы понять фиксировать изменения или отменить их. Если вы перехватите исключение и обработаете его в блоке atomic, вы можете скрыть от Django наличие проблемы. Это может привести к непредвиденному поведению.
В основном это относится к DatabaseError и классам наследникам, например IntegrityError. При таких ошибках транзакция будет сломана и Django выполнит отмену изменений после завершения блока atomic. Если вы попытаетесь выполнить запросы перед отменой изменений, Django вызовет исключение TransactionManagementError. Подобное поведение может произойти, если обработчик сигналов ORM вызовет исключение.
Правильный способ обработать ошибки базы данных – перехватить их за блоком atomic, как в примере выше. Если необходимо, добавьте дополнительный блок atomic для этого. У такого подхода есть свои преимущества: он явно разделяет операции, которые должны быть отменены при ошибке.
Если вы перехватите исключение, вызванное чистым SQL запросом, поведение Django будет зависеть от вашей базы данных.
Чтобы гарантировать атомарность, atomic блокирует некоторые функции. При попытке явно зафиксировать или отменить изменения, или изменить статус “autocommit” подключения к базе данных внутри блока atomic, будет вызвано исключение.
atomic принимает аргумент using, который обозначает имя базы данных с которой производится работа. Если этот аргумент не указан, то все действия идут относительно стандартной ("default") базы данных.
Код обработки транзакций в Django выполняет следующие действия:
создает транзакцию при входе в блок atomic;
создает точку сохранения при входе во вложенный блок atomic;
сохраняет или отменяет точку сохранения при выходе из вложенного блока;
фиксирует или отменяет транзакцию при выходе из последнего блока.
Вы можете отменить создание точки сохранения для внутренних блоков, указав в аргументе savepoint False. При исключении Django выполнит отмену изменений при выходе из первого попавшегося блока, который создает точку сохранения, или же самого последнего блока. Атомарность внешних транзакций все также гарантирована. Такой подход должен использоваться только для оптимизации создания точек сохранения, т.к. нарушает обработку ошибок, как было описано выше.
Вы можете использовать atomic при выключенном “autocommit”. Он использует только точки сохранения, даже для самого внешнего блока.
В предыдущих версиях самый внешний блок нельзя было объявить с savepoint=False при выключенном “autocommit”.
Заметка о производительности
Создание транзакции требует определенных операций от базы данных. Чтобы минимизировать нагрузку на сервер базы данных, делайте транзакции минимально короткими. Это особенно важно при использовании atomic() в долго выполняемых процессах вне цикла обработки запроса и ответа.
По стандартам SQL каждый SQL-запрос начинается с транзакции, если она еще не создана. Эти транзакции должны быть явно зафиксированы или отменены.
Это не всегда удобно при разработке приложения. Чтобы решить эту проблему, большинство баз данных поддерживает режим автоматической фиксации(autocommit). При включенном “autocommit”, если транзакция не активна, каждый SQL запрос обернут в транзакцию. То есть транзакция для каждого запроса не только создается, но и автоматически фиксируется, если запросы был успешно выполнен.
PEP 249, спецификация Python о Database API v2.0, требует чтобы “autocommit” по умолчанию был выключен. Django переопределяет это поведение и “autocommit” по умолчанию включен.
Вы можете отключить такое поведение, но мы не рекомендует этого делать.
Вы можете отключить управление транзакциями Django для определенной базы данных, установив AUTOCOMMIT в False в её настройках. При этом Django отключит “autocommit” и не будет выполнять фиксирование изменений. Все будут работать в соответствии с библиотекой, которая используется для работы с базой данных.
При этом вам необходимо явно фиксировать каждую транзакцию, не зависимо была ли она создана Django, или сторонней библиотекой. Это может быть полезно, если вы хотите использовать свой промежуточный слой(middleware) для управления транзакциями, или сделать что-то очень необычное.
Иногда вам необходимо выполнить какие-либо действия связанные с текущей транзакцией в базе данных, но только при успешном коммите транзакции. Это может быть задача Celery, отправка электронного письма, или сброс кэша.
Django предоставляет функцию on_commit(), которая позволяет добавить обработчик, вызываемый после успешного коммита транзакции:
Передайте функцию (которая не требует аргументов) в 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)
Можно также использовать lambda:
transaction.on_commit(lambda: some_celery_task.delay('arg1'))
Обработчик будет выполнен сразу после гипотетической записи в базу данных, которая означает успешное завершение транзакции.
Если вызывать 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.
Обработчики не вызываются, пока не будет восстановлен “autocommit” (т.к. любой запрос в обработчике неявно создаст транзакцию и заблокирует переход подключения в “autocommit” режим).
Если назначить обработчик в “autocommit” режиме вне блока atomic(), он будет выполнен моментально, а не после коммита.
Обработчики коммита транзакции работают только с “autocommit” режимом и atomic() (или при ATOMIC_REQUESTS) API транзакций. Вызвав on_commit() при отключенном “autocommit” и вне блока atomic приведет к ошибке.
Встроенный класс TestCase оборачивает каждый тест в транзакцию и отменяет её после выполнения теста, чтобы обеспечить изолированность тестов. Это означает, что ни одна транзакция никогда не коммитится, и обработчики on_commit() никогда не будут вызваны. Если вам нужно протестировать выполнение обработчика on_commit(), используйте TransactionTestCase.
Обработчик отмены транзакции реализовать на много сложнее т.к. причин отката на много больше.
Например, если подключение к базе данных оборвалось из-за внезапной остановки процесса, ваш обработчик отмены транзакции никогда не сможет выполниться.
Решение простое: вместо того, чтобы выполнить что-то в блоке atomic и затем отменять это при отмене транзакции, используйте on_commit(), чтобы отложить эти действия на момент успешного выполнения транзакции. На много проще отметить то, что никогда не выполнялось!
Предупреждение
При любой возможности используйте atomic(). Эта функция учитывает особенности различных баз данных и предотвращает использование недоступных операций.
Низкоуровневый API полезен только при реализации собственного управления транзакциями.
Django предоставляет простой API в модуле django.db.transaction для управления “autocommit” для каждого подключения к базе данных.
Каждая из этих функций принимает аргумент using, который обозначает имя базы данных. Если этот аргумент не указан, то все действия идут относительно стандартной ("default") базы данных.
“autocommit” по умолчанию включен. Если вы выключили его, на вашей ответственности и его включение.
При выключении “autocommit”, вы получаете поведение по умолчанию библиотеки, которая используется для работы с базой данных, и Django ничем вам не поможет. Это поведение описано в PEP 249, но реализация в различных библиотеках может отличаться. Вам следует изучить документацию используемой библиотеки.
Перед включением “autocommit” вы должны убедиться, что транзакция не активна, обычно выполнив commit() или rollback().
Django не позволит выключить “autocommit”, если активен блок atomic(), т.к. это нарушит атомарность.
Транзакция – это атомарный набор запросов к базе данных. Даже если ваша программа аварийно завершится, база данных гарантирует, что будут сохранены все изменения, или ни одно из них.
Django не предоставляет API для явного создания транзакции. Чтобы создать транзакцию, отключите “autocommit” с помощью set_autocommit().
Оказавшись в транзакции, можно зафиксировать выполненные изменения, используя функцию commit(), или отменить их через функцию rollback(). Эти функции находятся в модуле django.db.transaction.
Каждая из этих функций принимает аргумент using, который обозначает имя базы данных. Если этот аргумент не указан, то все действия идут относительно стандартной ("default") базы данных.
Django не позволит зафиксировать или отменить изменения, если блок atomic() активен, т.к. это нарушит атомарность.
Точкой сохранения называют маркер внутри транзакции, который позволяет вам отменить лишь часть транзакции, а не всю. Точки сохранения доступны при использовании бэкендов SQLite (≥ 3.6.8), PostgreSQL 8, Oracle и MySQL(при использовании InnoDB). Все остальные бэкенды предоставляют функции для создания точек сохранения, но они ничего не делают.
Точки сохранения бесполезны, если вы используете стандартное поведение Django – “autocommit”. Тем не менее, при создании транзакции через atomic(), каждая открытая транзакция выполняет ряд операций в базе данных, ожидая фиксации или отката транзакции. Если вы выполните откат транзакции, то будет выполнен откат всей транзакции. Точки сохранения предоставляют возможность выполнять частичный откат, вместо выполнения полного отката, который делается с помощью transaction.rollback().
При вложенных блоках atomic() создаются точки сохранения, которые позволяют выполнить частичную фиксацию или откат изменений. Мы настоятельно рекомендуем использовать atomic() вместо функций, указанных выше, но они в любом случае входят в публичный API и мы не собираемся удалять их.
Каждая из этих функция принимает аргумент using, который обозначает имя базы данных с которой производится работа. Если этот аргумент не указан, то все действия идут относительно стандартной ("default") базы данных.
Точки сохранения управляются тремя функциями из django.db.transaction:
Создаёт новую точку сохранения, обозначая точку в транзакции, отмечающую “хорошее” состояние. Возвращает ID (sid) точки сохранения.
Закрывает точку сохранения с указанным sid. Все изменения, выполненные до этой точки сохранения, будут добавлены в текущую транзакцию.
Откатывает транзакцию до точки сохранения с указанным sid.
Эти функции ничего не делают, если точки сохранения не поддерживаются базой данных, или если база данных в режиме “autocommit”.
Также Django предоставляет несколько дополнительных функций:
Сбрасывает счетчик генератора ID точек сохранения.
Следующие примеры демонстрируют использование промежуточных точек:
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(), отмена будет выполнена для всего блока, т.к. он не знает, что вы обработали ошибку на более низком уровне! Чтобы изменить это, вы можете контролировать поведение точек сохранения с помощью следующих функций.
Устанавливает флаг отмены изменений в True, форсируя отмену изменений при выходе из самого вложенного атомарного блока. Это может быть полезно для отмены изменений без вызова исключений.
Установив флаг в False, можно отключить отмену изменений. Перед тем как сделать это, убедитесь, что вы откатили транзакцию к последней безопасной точке в текущем атомарном блоке! Иначе вы нарушите атомарность и можете повредить данные.
Хотя SQLite ≥ 3.6.8 и поддерживает точки сохранения, из-за особенностей работы sqlite3 их сложно использовать.
При включенном “autocommit” точки сохранения не имеют смысла. При отключенном – sqlite3 неявно фиксирует изменения перед созданием точки сохранения. (На самом деле фиксация выполняет перед любой операцией отличной от SELECT, INSERT, UPDATE, DELETE и REPLACE.) Этот баг приводит к следующим особенностям:
При использовании MySQL поддержка транзакция зависит от версии движка базы данных и от используемого типа таблиц. (Под “типом таблицы” мы подразумеваем “InnoDB” или “MyISAM”.) Особенности транзакций MySQL выходят за рамки данной статьи, но сайт MySQL содержит информацию о `транзакциях<http://dev.mysql.com/doc/refman/5.6/en/sql-syntax-transactions.html>.
Если ваша настройка MySQL не поддерживает транзакции, тогда Django будет работать в режиме автоматической фиксации: Операторы будут выполняться и сразу же фиксироваться. Если же ваша настройка MySQL поддерживает транзакции, Django будет обрабатывать транзакции как описано выше.
Примечание
Этот раздел важен только при создании собственного управления транзакциями. Описанная проблема не возникнет при стандартном поведении 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() вызовет исключение.
Mar 31, 2016