Базы данных транзакции
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, durable=False)[source] -
Атомарность является определяющим свойством транзакций базы данных.
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!При выходе из блока
atomic, Django анализирует, произошел ли выход нормально или с исключением, чтобы определить, подтвердить или отменить изменения. Если вы перехватываете и обрабатываете исключения внутри блокаatomic, вы можете скрыть от Django факт возникновения проблемы. Это может привести к непредсказуемому поведению.Это главным образом относится к
DatabaseErrorи его подклассам, таким какIntegrityError. После такой ошибки транзакция нарушается, и Django выполнит откат в конце блокаatomic. Если вы попытаетесь выполнить запросы к базе данных до того, как произойдет откат, Django подниметTransactionManagementError. Вы также можете столкнуться с таким поведением, когда обработчик сигнала ORM вызывает исключение.Правильный способ перехвата ошибок базы данных — вокруг блока
atomic, как показано выше. При необходимости добавьте дополнительный блокatomicдля этой цели. Этот шаблон имеет еще одно преимущество: он явно определяет, какие операции будут отменены при возникновении исключения.Если вы перехватываете исключения, вызванные сырыми запросами SQL, поведение Django не определено и зависит от базы данных.
Возможно, вам придется вручную восстановить состояние приложения при отмене транзакции.
Значения полей модели не будут восстановлены при отмене транзакции. Это может привести к несовместимому состоянию модели, если вы вручную не восстановите исходные значения полей.
Например, в случае
MyModelс полемactive, этот фрагмент кода гарантирует, что проверкаif obj.activeв конце использует правильное значение, если обновлениеactiveнаTrueв транзакции завершается неудачно:from django.db import DatabaseError, transaction obj = MyModel(active=False) obj.active = True try: with transaction.atomic(): obj.save() except DatabaseError: obj.active = False if obj.active: ...Это также относится к любому другому механизму, который может хранить состояние приложения, такому как кэширование или глобальные переменные. Например, если код преднамеренно обновляет данные в кэше после сохранения объекта, рекомендуется использовать transaction.on_commit() вместо этого, чтобы отложить изменения в кэше до фактического подтверждения транзакции.
Для гарантии атомарности
atomicотключает некоторые API. Попытка подтвердить, отменить или изменить состояние автоматического подтверждения подключения к базе данных внутри блокаatomicприведет к возникновению исключения.atomicпринимает аргументusing, который должен быть именем базы данных. Если этот аргумент не указан, Django использует базу данных"default".Внутри Django код управления транзакциями:
- открывает транзакцию при входе в внешний блок
atomic; - создает точку сохранения при входе во внутренний блок
atomic; - освобождает или откатывает к точке сохранения при выходе из внутреннего блока;
- подтверждает или откатывает транзакцию при выходе из внешнего блока.
Вы можете отключить создание точек сохранения для внутренних блоков, установив аргумент
savepointвFalse. При возникновении исключения Django выполнит откат при выходе из первого родительского блока с точкой сохранения, если она существует, и внешнего блока в противном случае. Атомарность все еще гарантируется внешней транзакцией. Этот параметр следует использовать только в том случае, если накладные расходы на точки сохранения заметны. Он имеет недостаток в нарушении описанной выше обработки ошибок.Вы можете использовать
atomicпри отключенном автоматическом подтверждении. Он будет использовать только точки сохранения, даже для внешнего блока. - открывает транзакцию при входе в внешний блок
Учет производительности
Открытые транзакции связаны с затратами на производительность для сервера базы данных. Чтобы минимизировать эти накладные расходы, делайте транзакции как можно короче. Это особенно важно, если вы используете atomic() в длительных процессах за пределами цикла запроса/ответа Django.
Автоподтверждение
Почему Django использует автоподтверждение
В стандартах SQL каждый запрос SQL начинает транзакцию, если она ещё не активна. Такие транзакции должны быть явно подтверждены или отменены.
Это не всегда удобно для разработчиков приложений. Чтобы решить эту проблему, большинство баз данных предоставляют режим автоподтверждения. Когда автоподтверждение включено и транзакция не активна, каждый запрос SQL оборачивается в свою собственную транзакцию. Другими словами, каждый такой запрос не только начинает транзакцию, но и транзакция автоматически подтверждается или отменяется в зависимости от успешности выполнения запроса.
PEP 249, спецификация Python Database API v2.0, требует, чтобы автоподтверждение было изначально выключено. Django переопределяет это значение по умолчанию и включает автоподтверждение.
Чтобы этого избежать, можно отключить управление транзакциями, но это не рекомендуется.
Отключение управления транзакциями
Можно полностью отключить управление транзакциями Django для заданной базы данных, установив AUTOCOMMIT в значение False в её конфигурации. Если вы это сделаете, Django не включит автоподтверждение и не выполнит никаких подтверждений. Вы получите стандартное поведение библиотеки базы данных.
Это требует от вас явного подтверждения каждой транзакции, даже тех, которые инициированы Django или сторонними библиотеками. Поэтому это лучше всего использовать в ситуациях, когда вы хотите запустить собственный среднесвязующий модуль управления транзакциями или выполнить что-то действительно необычное.
Выполнение действий после подтверждения
Иногда вам нужно выполнить действие, связанное с текущей базой данных транзакции, но только в случае успешного подтверждения транзакции. Примерами могут служить фоновая задача, уведомление по электронной почте или очистка кэша.
on_commit() позволяет регистрировать обратные вызовы, которые будут выполнены после успешного подтверждения открытой транзакции:
-
on_commit(func, using=None, robust=False)[source]
Передайте функцию или любой вызываемый объект в on_commit():
from django.db import transaction def send_welcome_email(): ... transaction.on_commit(send_welcome_email)
Обратные вызовы не будут получать никаких аргументов, но вы можете связать их с functools.partial():
from functools import partial
for user in users:
transaction.on_commit(partial(send_invite_email, user=user))
Обратные вызовы вызываются после успешного подтверждения открытой транзакции. Если транзакция отменена (обычно при возникновении необработанного исключения в блоке atomic()), обратный вызов будет проигнорирован и никогда не будет вызван.
Если вы вызовете on_commit(), когда нет открытой транзакции, обратный вызов будет выполнен немедленно.
Иногда полезно регистрировать обратные вызовы, которые могут завершиться ошибкой. Передача robust=True позволяет выполнить последующие обратные вызовы, даже если текущий вызывает исключение. Все ошибки, производные от класса Python Exception , будут пойманы и записаны в журнал django.db.backends.base.
Можно использовать TestCase.captureOnCommitCallbacks() для тестирования обратных вызовов, зарегистрированных с on_commit().
Точки сохранения
Точки сохранения (т.е. вложенные блоки 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 для данной транзакции выполняются в порядке их регистрации.
Обработка исключений
Если одна функция on-commit, зарегистрированная с robust=False в данной транзакции вызывает необработанное исключение, последующие зарегистрированные функции в той же транзакции не будут выполнены. Это то же поведение, что если бы вы выполнили функции последовательно самостоятельно без on_commit().
Время выполнения
Обратные вызовы выполняются после успешного подтверждения, поэтому ошибка в обратном вызове не приведет к откату транзакции. Они выполняются условно при успехе транзакции, но они не являются частью транзакции. Для предполагаемых случаев использования (уведомления по электронной почте, фоновые задачи и т.д.) это должно быть нормально. Если нет (если ваше последующее действие настолько важно, что его ошибка должна означать ошибку самой транзакции), то вы не хотите использовать крючок on_commit(). Вместо этого вы можете использовать двухфазный протокол подтверждения, например, поддержку протокола двухфазного подтверждения psycopg и дополнительные расширения двухфазного подтверждения в спецификации Python DB-API.
Обратные вызовы не выполняются, пока режим автоподтверждения не будет восстановлен на подключении после подтверждения (поскольку в противном случае любые запросы, выполненные в обратном вызове, откроют неявную транзакцию, препятствуя подключению вернуться в режим автоподтверждения).
В режиме автоподтверждения и вне блока atomic() функция будет выполняться немедленно, а не при подтверждении.
Функции on-commit работают только с режимом автоподтверждения и API транзакций atomic() (или ATOMIC_REQUESTS). Вызов on_commit() при отключенном автоподтверждении и вне блока атомарных операций приведет к ошибке.
Использование в тестах
Класс Django TestCase оборачивает каждый тест в транзакцию и откатывает эту транзакцию после каждого теста, чтобы обеспечить изоляцию тестов. Это означает, что никакая транзакция фактически не подтверждается, поэтому ваши обратные вызовы on_commit() никогда не будут выполнены.
Вы можете обойти это ограничение, используя TestCase.captureOnCommitCallbacks(). Это позволяет захватывать ваши обратные вызовы on_commit() в список, что позволяет делать утверждения о них или эмулировать подтверждение транзакции, вызывая их.
Другой способ обойти это ограничение — использовать TransactionTestCase вместо TestCase. Это означает, что ваши транзакции будут подтверждаться, и обратные вызовы будут выполнены. Однако TransactionTestCase очищает базу данных между тестами, что значительно медленнее, чем изоляция TestCase.
Почему нет обработчика отката?
Обработчик отката труднее реализовать надёжно, чем обработчик подтверждения, поскольку существует множество причин для неявного отката.
Например, если подключение к базе данных прерывается, потому что ваш процесс был завершен без возможности благополучного завершения, ваш обработчик отката никогда не будет запущен.
Но есть решение: вместо того, чтобы что-то делать в блоке атомарных операций (транзакции), а затем отменять это, если транзакция терпит неудачу, используйте 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, 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 предотвращает такой откат. Перед этим убедитесь, что вы откатили транзакцию до известной хорошей сохраняющей точки внутри текущего атомарного блока! В противном случае вы нарушите атомарность, и может произойти повреждение данных.
Особенности баз данных
Сохраняющие точки в 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/5.1/topics/db/transactions/