Spec-Zone.ru › Django 2.1

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

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) [source]

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

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

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 использует автоподтверждение

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

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

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

END_OF_DOCUMENT_MARKER ```

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

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

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

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

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

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

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

on_commit(func, using=None) [source]

Передайте любую функцию (не принимающую аргументы) в 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() никогда не будут запущены. Если вам нужно проверить результаты функции обратного вызова on_commit(), используйте TransactionTestCase вместо этого.

Почему нет обработчика отката?

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

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

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

Spec-Zone.ru

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