Базы данных транзакции
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 не определено и зависит от базы данных.
Для гарантии атомарности
atomicотключает некоторые API. Попытка подтвердить, отменить или изменить состояние автоматического подтверждения подключения к базе данных внутри блокаatomicвызовет исключение.atomicпринимает аргументusing, который должен быть именем базы данных. Если этот аргумент не указан, Django использует базу данных"default".Внутри Django код управления транзакциями:
- открывает транзакцию при входе в самый внешний блок
atomic; - создаёт точку сохранения при входе во внутренний блок
atomic; - освобождает или откатывает до точки сохранения при выходе из внутреннего блока;
- подтверждает или отменяет транзакцию при выходе из самого внешнего блока.
Вы можете отключить создание точек сохранения для вложенных блоков, задав аргумент
savepointв значениеFalse. Если произойдёт исключение, Django выполнит откат при выходе из первого родительского блока с точкой сохранения, если она есть, и из самого внешнего блока в противном случае. Атомарность всё ещё гарантируется внешней транзакцией. Этот параметр следует использовать только если накладные расходы на точки сохранения заметны. Он имеет недостаток в том, что нарушает описанную выше обработку ошибок.Вы можете использовать
atomicпри отключённом автоматическом подтверждении. Он будет использовать только точки сохранения, даже для самого внешнего блока.Ранее самый внешний атомарный блок нельзя было объявить с
savepoint=Falseпри отключённом автоматическом подтверждении. - открывает транзакцию при входе в самый внешний блок
Рекомендации по производительности
Открытые транзакции имеют негативное влияние на производительность сервера базы данных. Чтобы минимизировать эту нагрузку, сохраняйте транзакции как можно короче. Это особенно важно, если вы используете atomic() в длительных процессах, вне цикла запроса/ответа Django.
Автоподтверждение
Почему Django использует автоподтверждение
В стандартах SQL каждый SQL-запрос начинает транзакцию, если она уже не активна. Такие транзакции затем должны быть явно подтверждены или отменены.
Это не всегда удобно для разработчиков приложений. Чтобы решить эту проблему, большинство баз данных предоставляют режим автоматического подтверждения. Когда автоподтверждение включено и нет активной транзакции, каждый SQL-запрос оборачивается в свою собственную транзакцию. Другими словами, каждый такой запрос не только начинает транзакцию, но и транзакция автоматически подтверждается или отменяется в зависимости от успешности выполнения запроса.
PEP 249, спецификация API баз данных Python версии 2.0, требует первоначального отключения автоподтверждения. Django переопределяет это значение по умолчанию и включает автоподтверждение.
Чтобы избежать этого, вы можете отключить управление транзакциями, но это не рекомендуется.
Отключение управления транзакциями
Вы можете полностью отключить управление транзакциями Django для данной базы данных, установив AUTOCOMMIT в значение False в её конфигурации. Если вы это сделаете, Django не включит автоматическое подтверждение и не выполнит никаких подтверждений. Вы получите стандартное поведение библиотеки базы данных.
Это требует от вас явного подтверждения каждой транзакции, даже тех, что были начаты Django или сторонними библиотеками. Таким образом, это лучше всего использовать в ситуациях, когда вы хотите запустить собственный middleware для управления транзакциями или сделать что-то действительно необычное.
Выполнение действий после подтверждения
Иногда вам нужно выполнить действие, связанное с текущей транзакцией базы данных, но только если транзакция успешно подтверждается. Примеры могут включать задачу Celery, уведомление по электронной почте или очистку кэша.
Django предоставляет функцию on_commit() для регистрации функций обратного вызова, которые должны выполняться после успешного подтверждения транзакции:
-
on_commit(func, using=None)[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 (≥ 3.6.8), 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 ≥ 3.6.8 поддерживает точки сохранения, недостаток в проектировании модуля 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/1.9/topics/db/transactions/