Spec-Zone.ru › Django 1.11

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

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, the Python Database API Specification v2.0, requires autocommit to be initially turned off. Django overrides this default and turns autocommit on.

To avoid this, you can отключить управление транзакциями, но это не рекомендуется.

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

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

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

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

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

Spec-Zone.ru

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