Spec-Zone.ru › Django 4.2

Базы данных транзакции

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!

При выходе из блока 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:
    ...

Для обеспечения атомарности atomic отключает некоторые API. Попытка подтвердить, отменить или изменить состояние автоматического подтверждения соединения с базой данных внутри блока atomic вызовет исключение.

atomic принимает аргумент using, который должен быть именем базы данных. Если этот аргумент не указан, Django использует базу данных "default".

Внутри Django код управления транзакциями:

  • открывает транзакцию при входе в самый внешний блок atomic;
  • создаёт точку сохранения при входе во внутренний блок atomic;
  • освобождает или откатывает к точке сохранения при выходе из внутреннего блока;
  • подтверждает или откатывает транзакцию при выходе из самого внешнего блока.

Вы можете отключить создание точек сохранения для внутренних блоков, установив аргумент savepoint в False. Если произойдёт исключение, Django выполнит откат при выходе из первого родительского блока с точкой сохранения, если она есть, и из самого внешнего блока в противном случае. Атомарность всё ещё гарантируется внешней транзакцией. Этот параметр следует использовать только в том случае, если накладные расходы на точки сохранения заметны. У него есть недостаток: он нарушает описанную выше обработку ошибок.

Вы можете использовать atomic при выключенном автоматическом подтверждении. Он будет использовать только точки сохранения, даже для самого внешнего блока.

Соображения по производительности

Открытые транзакции имеют стоимость производительности для сервера базы данных. Чтобы минимизировать эти накладные расходы, старайтесь держать ваши транзакции как можно короче. Это особенно важно, если вы используете atomic() в долговременных процессах, вне цикла запроса/ответа Django.

Изменено в Django 4.1:

В более ранних версиях проверка надёжности отключалась в django.test.TestCase.

Автоподтверждение

Зачем 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)

Передайте функцию или любой вызываемый объект в 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().

Изменено в Django 4.2:

Аргумент robust был добавлен.

Точки сохранения

Точки сохранения (т.е. вложенные блоки 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()

Порядок выполнения

Функции с обратным вызовом для данной транзакции выполняются в том порядке, в котором они были зарегистрированы.

Обработка исключений

Если одна функция с обратным вызовом, зарегистрированная с помощью robust=False в рамках данной транзакции, вызывает необработанное исключение, последующие зарегистрированные функции в этой же транзакции не будут выполнены. Это поведение аналогично последовательному выполнению функций без on_commit().

Изменено в Django 4.2:

Аргумент robust был добавлен.

Время выполнения

Обратные вызовы выполняются после успешного подтверждения, поэтому ошибка в обратном вызове не приведет к откату транзакции. Они выполняются условно при успешном выполнении транзакции, но не являются частью транзакции. Для предполагаемых случаев использования (уведомления по электронной почте, фоновые задачи и т.д.) этого должно быть достаточно. Если это не так (если ваше последующее действие настолько важно, что его ошибка должна означать ошибку самой транзакции), то вы не хотите использовать крючок on_commit(). Вместо этого вы можете использовать двухфазный протокол подтверждения, такой как поддержка двухфазного протокола подтверждения в psycopg и дополнительные расширения двухфазного подтверждения в спецификации Python DB-API.

Обратные вызовы не выполняются до тех пор, пока режим автозаписи не будет восстановлен на подключении после подтверждения (иначе любые запросы, выполненные в обратном вызове, откроют неявную транзакцию, предотвращая возврат соединения в режим автозаписи).

Когда вы находитесь в режиме автозаписи и вне блока atomic(), функция выполнится немедленно, а не при подтверждении.

Функции с обратными вызовами работают только с режимом автозаписи и API транзакций atomic() (или ATOMIC_REQUESTS). Вызов on_commit() при отключенном автозаписи и без нахождения в блоке atomic приведет к ошибке.

Использование в тестах

Класс Django TestCase оборачивает каждый тест в транзакцию и отменяет эту транзакцию после каждого теста, чтобы обеспечить изоляцию тестов. Это означает, что транзакция никогда фактически не подтверждается, поэтому ваши обратные вызовы 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 предотвращает такой откат. Прежде чем сделать это, убедитесь, что вы откатили транзакцию до известной хорошей точки сохранения внутри текущего атомарного блока! В противном случае вы нарушите атомарность, и может произойти повреждение данных.

Особенности работы с базами данных

Точки сохранения в SQLite

Хотя SQLite поддерживает точки сохранения, ошибка в конструкции модуля sqlite3 делает их почти непригодными для использования.

Когда включена автозапись, точки сохранения не имеют смысла. Когда она выключена, sqlite3 неявно подтверждает перед операторами savepoint. (На самом деле, она подтверждает перед любым оператором, кроме 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/4.2/topics/db/transactions/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API