Базы данных транзакции
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) -
Этот декоратор аннулирует действие
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: ...Для гарантии атомарности
atomicотключает некоторые API. Попытка подтвердить, отменить или изменить состояние автосохранения соединения с базой данных внутри блокаatomicвызовет исключение.atomicпринимает аргументusing, который должен быть именем базы данных. Если этот аргумент не указан, Django использует базу данных"default".Внутри Django код управления транзакциями:
- открывает транзакцию при входе в самый внешний блок
atomic; - создает точку сохранения при входе во внутренний блок
atomic; - освобождает или откатывает к точке сохранения при выходе из внутреннего блока;
- подтверждает или отменяет транзакцию при выходе из самого внешнего блока.
Вы можете отключить создание точек сохранения для внутренних блоков, установив аргумент
savepointвFalse. Если произойдет исключение, Django выполнит откат при выходе из первого родительского блока с точкой сохранения, если она есть, и из самого внешнего блока в противном случае. Атомарность все еще гарантируется внешней транзакцией. Этот параметр следует использовать только в том случае, если накладные расходы точек сохранения заметны. Он имеет недостаток нарушения описанной выше обработки ошибок.Вы можете использовать
atomicкогда автосохранение выключено. Он будет использовать только точки сохранения, даже для самого внешнего блока. - открывает транзакцию при входе в самый внешний блок
Соображения по производительности
Открытые транзакции имеют стоимость производительности для вашего сервера базы данных. Чтобы минимизировать эту нагрузку, старайтесь держать транзакции как можно короче. Это особенно важно, если вы используете atomic() в длительно работающих процессах, вне цикла запроса/ответа Django.
Предупреждение
Класс django.test.TestCase отключает проверку устойчивости, чтобы позволить тестирование устойчивых атомарных блоков в транзакции по соображениям производительности. Используйте django.test.TransactionTestCase для тестирования устойчивости.
Аргумент durable был добавлен.
Автозапись
Почему Django использует автозапись
В стандартах SQL каждый запрос SQL запускает транзакцию, если таковая не активна. Такие транзакции должны быть явно подтверждены или отменены.
Это не всегда удобно для разработчиков приложений. Для решения этой проблемы большинство баз данных предоставляют режим автозаписи. Когда автозапись включена и нет активной транзакции, каждый запрос SQL выполняется в своей собственной транзакции. Другими словами, каждый такой запрос не только начинает транзакцию, но транзакция также автоматически подтверждается или отменяется в зависимости от того, выполнился ли запрос.
PEP 249, спецификация API Python для баз данных версии 2.0, требует, чтобы автозапись была изначально выключена. Django переопределяет этот параметр по умолчанию и включает автозапись.
Чтобы избежать этого, вы можете отключить управление транзакциями, но это не рекомендуется.
Отключение управления транзакциями
Вы можете полностью отключить управление транзакциями Django для заданной базы данных, установив AUTOCOMMIT в значение False в её конфигурации. Если вы это сделаете, Django не включит автозапись и не выполнит никаких подтверждений. Вы получите стандартное поведение библиотеки баз данных.
Это требует от вас явного подтверждения каждой транзакции, даже тех, которые были начаты Django или сторонними библиотеками. Поэтому это лучше всего использовать в ситуациях, когда вы хотите запустить свой собственный посредник управления транзакциями или сделать что-то действительно необычное.
Выполнение действий после подтверждения
Иногда вам нужно выполнить действие, связанное с текущей базой данных транзакции, но только в случае успешного подтверждения транзакции. Примерами могут служить задача Celery, уведомление по электронной почте или очистка кэша.
Django предоставляет функцию on_commit() для регистрации функций обратного вызова, которые должны быть выполнены после успешного подтверждения транзакции:
-
on_commit(func, using=None)
Передайте любую функцию (не принимающую аргументов) в 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() при отключённой автозаписи и без блока atomic приведёт к ошибке.
Использование в тестах
Класс TestCase Django оборачивает каждый тест в транзакцию и отменяет эту транзакцию после каждого теста, чтобы обеспечить изоляцию тестов. Это означает, что ни одна транзакция фактически не подтверждается, поэтому ваши обратные вызовы on_commit() никогда не будут выполнены.
Вы можете преодолеть это ограничение, используя TestCase.captureOnCommitCallbacks(). Это позволяет захватить ваши обратные вызовы on_commit() в список, что позволяет делать утверждения относительно них или эмулировать подтверждение транзакции, вызывая их.
Другой способ преодолеть ограничение — использовать TransactionTestCase вместо TestCase. Это означает, что ваши транзакции будут подтверждаться, а обратные вызовы будут выполняться. Однако TransactionTestCase очищает базу данных между тестами, что значительно медленнее, чем изоляция TestCase.
Почему нет обработчика отката?
Обработчик отката сложнее реализовать надёжно, чем обработчик подтверждения, так как множество факторов может вызвать неявный откат.
Например, если соединение с базой данных прерывается, потому что ваш процесс был завершён без возможности плавного завершения, ваш обработчик отката никогда не будет запущен.
Но есть решение: вместо выполнения чего-то в блоке atomic (транзакции), а затем отмены этого, если транзакция потерпит неудачу, используйте on_commit(), чтобы отложить его выполнение до подтверждения транзакции. Гораздо проще отменить то, что вы никогда не делали в первую очередь!
API низкого уровня
Предупреждение
Всякий раз, когда это возможно, отдавайте предпочтение atomic(). Это учитывает особенности каждой базы данных и предотвращает некорректные операции.
API низкого уровня полезны только в том случае, если вы реализуете собственное управление транзакциями.
Автозапись
Django предоставляет API в модуле django.db.transaction для управления состоянием автозаписи каждого соединения с базой данных.
-
get_autocommit(using=None)
-
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 принудительно откатывает транзакцию при выходе из самого внутреннего блока atomic. Это может быть полезно для запуска отката без повышения исключения.
Установка на False предотвращает такой откат. Перед этим убедитесь, что вы откатили транзакцию до известной хорошей сохраняемой точки в текущем блоке atomic! В противном случае вы нарушаете атомарность, и может произойти повреждение данных.
Примечания, специфичные для базы данных
Сохраняемые точки в SQLite
Хотя SQLite поддерживает сохраняемые точки, недостаток в проектировании модуля sqlite3 делает их практически непригодными для использования.
Когда включено автоматическое сохранение, сохраняемые точки бессмысленны. Когда это выключено, sqlite3 неявно подтверждает перед операциями сохраняемых точек. (Фактически, он подтверждает перед любой операцией, кроме SELECT, INSERT, UPDATE, DELETE и REPLACE.) Эта ошибка имеет два следствия:
- API низкого уровня для сохраняемых точек применимы только внутри транзакции, т.е. внутри блока
atomic(). - Невозможно использовать
atomic(), когда автоматическое сохранение выключено.
Транзакции в MySQL
Если вы используете MySQL, ваши таблицы могут или не могут поддерживать транзакции; это зависит от версии MySQL и типов таблиц, которые вы используете. (Под «типами таблиц» мы подразумеваем, например, «InnoDB» или «MyISAM».) Особенности транзакций MySQL выходят за рамки этой статьи, но на сайте MySQL есть информация о транзакциях MySQL.
Если ваша настройка MySQL не поддерживает транзакции, Django всегда будет работать в режиме автоматического сохранения: операторы будут выполняться и сохраняться сразу после их вызова. Если ваша настройка MySQL поддерживает транзакции, Django будет обрабатывать транзакции, как описано в этом документе.
Обработка исключений в транзакциях PostgreSQL
Примечание
Этот раздел актуален только в том случае, если вы реализуете собственную систему управления транзакциями. Эта проблема не может возникнуть в стандартном режиме Django, и atomic() обрабатывает её автоматически.
Внутри транзакции, когда вызов курсора PostgreSQL вызывает исключение (обычно IntegrityError), все последующие SQL-запросы в той же транзакции завершатся ошибкой «текущая транзакция прервана, запросы игнорируются до конца блока транзакции». Хотя базовое использование save() в PostgreSQL, скорее всего, не вызовет исключение, существуют более сложные сценарии использования, которые могут, такие как сохранение объектов с уникальными полями, сохранение с флагом force_insert/force_update или вызов пользовательского SQL.
Существует несколько способов восстановления после такого рода ошибки.
Откат транзакции
Первый вариант — откатить всю транзакцию. Например:
a.save() # Succeeds, but may be undone by transaction rollback
try:
b.save() # Could throw exception
except IntegrityError:
transaction.rollback()
c.save() # Succeeds, but a.save() may have been undone
Вызов transaction.rollback() откатывает всю транзакцию. Любые незафиксированные операции базы данных будут потеряны. В этом примере изменения, внесённые a.save(), будут утеряны, даже если эта операция сама не вызвала ошибку.
Откат сохраняемой точки
Вы можете использовать сохраняемые точки для управления объёмом отката. Перед выполнением операции с базой данных, которая может завершиться ошибкой, вы можете установить или обновить сохраняемую точку; таким образом, если операция завершится ошибкой, вы сможете откатить только проблемную операцию, а не всю транзакцию. Например:
a.save() # Succeeds, and never undone by savepoint rollback
sid = transaction.savepoint()
try:
b.save() # Could throw exception
transaction.savepoint_commit(sid)
except IntegrityError:
transaction.savepoint_rollback(sid)
c.save() # Succeeds, and a.save() is never undone
В этом примере a.save() не будет отменён в случае, если b.save() вызовет исключение.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/3.2/topics/db/transactions/