Spec-Zone.ru › Django 5.2

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

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.

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

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

Функции on-commit работают только с режимом автоподтверждения и 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) [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 предотвращает такой откат. Прежде чем делать это, убедитесь, что вы откатили транзакцию до известной хорошей точки сохранения в текущем блоке 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/5.2/topics/db/transactions/

Spec-Zone.ru

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