Базы данных транзакции
Django предоставляет несколько способов управления транзакциями базы данных.
Управление транзакциями базы данных
Поведение Django по умолчанию для транзакций
По умолчанию Django работает в режиме автоматического подтверждения. Каждый запрос сразу же подтверждается в базе данных, если транзакция не активна. См. подробности ниже.
Django автоматически использует транзакции или сохраняемые точки для обеспечения целостности операций ORM, требующих нескольких запросов, особенно запросов delete() и update().
Класс Django TestCase также оборачивает каждый тест в транзакцию по соображениям производительности.
Связывание транзакций с HTTP-запросами
Один из распространённых способов обработки транзакций в веб-приложениях — это обертка каждого запроса в транзакцию. Установите ATOMIC_REQUESTS в значение True в настройках каждой базы данных, для которой вы хотите включить это поведение.
Это работает следующим образом. Перед вызовом функции представления Django запускает транзакцию. Если ответ генерируется без проблем, Django подтверждает транзакцию. Если представление вызывает исключение, Django откатывает транзакцию.
Вы можете выполнить подтранзакции, используя сохраняемые точки в коде представления, обычно с помощью контекстного менеджера atomic(). Однако в конце представления все изменения будут подтверждены или все будут отменены.
Предупреждение
Хотя простота этой модели транзакций привлекательна, она также делает её неэффективной при увеличении трафика. Открытие транзакции для каждого представления несет определённые накладные расходы. Воздействие на производительность зависит от шаблонов запросов вашего приложения и от того, как ваша база данных обрабатывает блокировку.
Транзакции на каждый запрос и потоковые ответы
Когда представление возвращает StreamingHttpResponse, чтение содержимого ответа часто выполняет код для генерации содержимого. Поскольку представление уже вернуло результат, такой код выполняется вне транзакции.
В общем случае не рекомендуется записывать в базу данных во время генерации потокового ответа, поскольку нет разумного способа обработки ошибок после начала отправки ответа.
На практике эта функция просто оборачивает каждую функцию представления в декоратор atomic(), описанный ниже.
Обратите внимание, что в транзакции заключено только выполнение вашего представления. Средства обработки запросов и отображение шаблонов выполняются вне транзакции.
Когда 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!При выходе из блока
atomic, Django проверяет, завершился ли он нормально или с исключением, чтобы определить, подтвердить или отменить изменения. Если вы перехватываете и обрабатываете исключения внутри блока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при отключённом автоматическом подтверждении. Он будет использовать только сохраняемые точки, даже для внешнего блока. - открывается транзакция при входе в внешний блок
Соображения по производительности
Открытые транзакции имеют производительные затраты для сервера базы данных. Чтобы минимизировать эти накладные расходы, делайте транзакции как можно короче. Это особенно важно, если вы используете atomic() в длительных процессах вне цикла запроса/ответа Django.
Автоподтверждение
Зачем Django использует автоподтверждение
В стандартах SQL каждый SQL-запрос начинает транзакцию, если она уже не активна. Такие транзакции должны быть явно подтверждены или отменены.
Это не всегда удобно для разработчиков приложений. Чтобы решить эту проблему, большинство баз данных предоставляют режим автоматического подтверждения. Когда автоматическое подтверждение включено и транзакция не активна, каждый SQL-запрос оборачивается в свою собственную транзакцию. Другими словами, каждый такой запрос не только запускает транзакцию, но и транзакция автоматически подтверждается или откатывается в зависимости от успеха запроса.
PEP 249, спецификация API базы данных Python 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() никогда не будут выполнены. Если вам нужно протестировать результаты обратного вызова 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.10/topics/db/transactions/