Spec-Zone.ru › Django 2.0

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

Django предоставляет несколько способов управления транзакциями базы данных.

Управление транзакциями базы данных

Поведение транзакций по умолчанию в Django

По умолчанию Django работает в режиме автоподтверждения. Каждый запрос сразу же подтверждается в базе данных, если нет активной транзакции. Подробнее см. ниже.

Django автоматически использует транзакции или точки сохранения для обеспечения целостности операций ORM, требующих нескольких запросов, особенно запросов delete() и update().

Класс Django TestCase также оборачивает каждый тест в транзакцию по соображениям производительности.

Связывание транзакций с HTTP-запросами

Распространённый способ обработки транзакций в веб-приложениях — это вложение каждого запроса в транзакцию. Установите ATOMIC_REQUESTS в значение True в конфигурации каждой базы данных, для которой вы хотите включить это поведение.

Работает это так. Перед вызовом функции представления Django запускает транзакцию. Если ответ сгенерирован без проблем, Django подтверждает транзакцию. Если представление вызывает исключение, Django откатывает транзакцию.

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

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

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

Транзакции на запрос и потоковые ответы

Когда представление возвращает StreamingHttpResponse, чтение содержимого ответа часто выполняет код для генерации содержимого. Поскольку представление уже возвратило, такой код выполняется вне транзакции.

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

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

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

Когда ATOMIC_REQUESTS включен, все еще можно предотвратить выполнение представлений в транзакции.

non_atomic_requests(using=None) [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 переопределяет этот параметр и включает автоподтверждение.

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

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

Для отключения управления транзакциями 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(), функция будет выполняться немедленно, а не при подтверждении.

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

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

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

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

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

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

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

Spec-Zone.ru

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