Spec-Zone.ru › Django 4.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:
    ...

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

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

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

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

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

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

Учёт производительности

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

Предупреждение

Класс django.test.TestCase отключает проверку надёжности, чтобы разрешить тестирование атомарных блоков с гарантией надёжности в транзакции по соображениям производительности. Используйте django.test.TransactionTestCase для тестирования надёжности.

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

Добавлен аргумент durable.

Автозапись

Почему Django использует автозапись

В стандартах SQL каждый запрос SQL начинает транзакцию, если она не активна. Такие транзакции должны быть явно подтверждены или отменены.

Это не всегда удобно для разработчиков приложений. Чтобы решить эту проблему, большинство баз данных предоставляют режим автозаписи. Когда автозапись включена и нет активной транзакции, каждый запрос SQL оборачивается в свою транзакцию. Другими словами, каждый такой запрос не только начинает транзакцию, но и транзакция автоматически подтверждается или отменяется в зависимости от успешности запроса.

PEP 249, спецификация Python Database API v2.0, требует, чтобы автозапись была изначально выключена. Django переопределяет это значение по умолчанию и включает автозапись.

Чтобы этого избежать, вы можете отключить управление транзакциями, но это не рекомендуется.

Отключение управления транзакциями

Вы можете полностью отключить управление транзакциями Django для конкретной базы данных, установив AUTOCOMMIT в значение False в её конфигурации. Если вы это сделаете, Django не включит автозапись и не будет выполнять никаких подтверждений. Вы получите обычное поведение подлежащей библиотеки базы данных.

Это требует от вас явного подтверждения каждой транзакции, даже тех, которые были начаты Django или сторонними библиотеками. Таким образом, это лучше всего использовать в ситуациях, когда вы хотите запустить свой собственный middleware управления транзакциями или сделать что-то действительно необычное.

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

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

Django предоставляет функцию on_commit() для регистрации функций обратного вызова, которые должны быть выполнены после успешного подтверждения транзакции:

on_commit(func, using=None)

Передайте любую функцию (без аргументов) в on_commit():

from django.db import transaction

def do_something():
    pass  # send a mail, invalidate a cache, fire off a Celery task, etc.

transaction.on_commit(do_something)

Вы также можете обернуть свою функцию в лямбда-выражение:

transaction.on_commit(lambda: some_celery_task.delay('arg1'))

Функция, которую вы передаёте, будет вызвана сразу после гипотетической записи в базу данных, где on_commit() вызывается, будет успешно подтверждена.

Если вы вызовете on_commit() в то время, когда нет активной транзакции, функция обратного вызова будет выполнена немедленно.

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

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

Точки сохранения (то есть вложенные блоки atomic()) обрабатываются корректно. То есть вызываемый объект on_commit(), зарегистрированный после точки сохранения (во вложенном блоке atomic()), будет вызван после подтверждения внешней транзакции, но не если произошла отмена к этой точке сохранения или любой предыдущей точке сохранения во время транзакции:

with transaction.atomic():  # Outer atomic, start a new transaction
    transaction.on_commit(foo)

    with transaction.atomic():  # Inner atomic block, create a savepoint
        transaction.on_commit(bar)

# foo() and then bar() will be called when leaving the outermost block

С другой стороны, когда точка сохранения отменяется (из-за возникновения исключения), внутренний вызываемый объект не будет вызван:

with transaction.atomic():  # Outer atomic, start a new transaction
    transaction.on_commit(foo)

    try:
        with transaction.atomic():  # Inner atomic block, create a savepoint
            transaction.on_commit(bar)
            raise SomeError()  # Raising an exception - abort the savepoint
    except SomeError:
        pass

# foo() will be called, but not bar()

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

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

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

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

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

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

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

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

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

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

Класс TestCase Django оборачивает каждый тест в транзакцию и отменяет эту транзакцию после каждого теста, чтобы обеспечить изоляцию тестов. Это означает, что никакая транзакция никогда не подтверждается фактически, поэтому ваши обратные вызовы on_commit() никогда не будут выполнены.

Вы можете обойти это ограничение, используя TestCase.captureOnCommitCallbacks(). Это позволяет захватить ваши обратные вызовы on_commit() в списке, позволяя делать утверждения о них или имитировать подтверждение транзакции, вызывая их.

Другой способ обойти ограничение — использовать TransactionTestCase вместо TestCase. Это означает, что ваши транзакции будут подтверждены, и обратные вызовы будут выполнены. Однако TransactionTestCase сбрасывает базу данных между тестами, что значительно медленнее, чем изоляция TestCase.

Почему нет крючка отмены?

Крючок отмены сложнее реализовать надёжно, чем крючок подтверждения, так как множество факторов могут привести к неявной отмене.

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

Но есть решение: вместо того, чтобы делать что-то во время атомарного блока (транзакции), а затем отменять это, если транзакция завершается неуспешно, используйте 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/4.0/topics/db/transactions/

Spec-Zone.ru

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