Spec-Zone.ru › Django 3.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)

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

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

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

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

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

Spec-Zone.ru

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