Spec-Zone.ru › Django 5.0

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

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)

Этот декоратор аннулирует эффект 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:
    ...

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

Выполнение действий после подтверждения

Иногда вам нужно выполнить действие, связанное с текущей базой данных, но только если транзакция успешно подтверждена. Примеры могут включать фоновую задачу, уведомление по электронной почте или очистку кэша.

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 позволяет последующим обратным вызовам выполняться, даже если текущий вызов генерирует исключение. Все ошибки, производные от класса Exception Python, перехватываются и регистрируются в логгер 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()

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

Функции on-commit для данной транзакции выполняются в порядке их регистрации.

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

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

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

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

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

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

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

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

Функции on-commit работают только с режимом автоподтверждения и 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 принудительно вызывает откат при выходе из самого внутреннего атомарного блока. Это может быть полезно для запуска отката без поднятия исключения.

Установка на 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.0/topics/db/transactions/

Spec-Zone.ru

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