Транзакции базы данных

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

non_atomic_requests( используя = 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( используется = Нет , точка сохранения = Истина , долговечность = Ложь )

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

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

Иногда полезно убедиться, что atomicблок всегда является самым внешним atomicблоком, гарантируя, что любые изменения базы данных будут зафиксированы при выходе из блока без ошибок. Это называется долговечностью и достигается за счет схватывания durable=True. Если atomicблок вложен в другой, возникает ошибка RuntimeError.

atomicможно использовать как декоратор :

from django.db import transaction

@transaction.atomic
def viewfunc(request):
    # This code executes inside a transaction.
    do_stuff()

и как менеджер контекста :

from django.db import transaction

def viewfunc(request):
    # This code executes in autocommit mode (Django's default).
    do_stuff()

    with transaction.atomic():
        # This code executes inside a transaction.
        do_more_stuff()

Заключение atomicв блок try / except позволяет естественным образом обрабатывать ошибки целостности:

from django.db import IntegrityError, transaction

@transaction.atomic
def viewfunc(request):
    create_parent()

    try:
        with transaction.atomic():
            generate_relationships()
    except IntegrityError:
        handle_exception()

    add_children()

В этом примере, даже если generate_relationships()вызывает ошибку базы данных из-за нарушения ограничения целостности, вы можете выполнять запросы в add_children(), и изменения из create_parent()все еще существуют и связаны с той же транзакцией. Обратите внимание, что любые операции, предпринятые в generate_relationships(), уже будут безопасно откатаны при handle_exception()вызове, поэтому обработчик исключений также может работать с базой данных при необходимости.

Избегайте перехвата исключений внутри atomic!

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

Это в основном касается DatabaseErrorи его подклассов, таких как IntegrityError. После такой ошибки транзакция прерывается, и Django выполнит откат в конце atomicблока. Если вы попытаетесь выполнить запросы к базе данных до того, как произойдет откат, Django вызовет файл TransactionManagementError. Вы также можете столкнуться с таким поведением, когда обработчик сигналов, связанный с ORM, вызывает исключение.

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

Если вы перехватываете исключения, вызванные необработанными SQL-запросами, поведение Django не определено и зависит от базы данных.

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

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

Например, если MyModelс activeполем, этот фрагмент гарантирует , что проверка в конце использует правильное значение , если обновление для терпит неудачу в сделке:if obj.activeactiveTrue

from django.db import DatabaseError, transaction

obj = MyModel(active=False)
obj.active = True
try:
    with transaction.atomic():
        obj.save()
except DatabaseError:
    obj.active = False

if obj.active:
    ...

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

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

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

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

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

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

Соображения производительности

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

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

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

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

durableАргумент был добавлен.

Автокоммит

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

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

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

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

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

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

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

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

Выполнение действий после коммита

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

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

on_commit( func , используя = 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().

Сроки исполнения

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

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

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

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

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

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

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

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

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

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

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

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

API низкого уровня

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

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

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

Автокоммит

Django предоставляет API в django.db.transactionмодуле для управления состоянием автоматической фиксации каждого соединения с базой данных.

get_autocommit( используя = None )
set_autocommit( autocommit , using = None )

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

Автокоммит изначально включен. Если вы выключите его, вы обязаны восстановить его.

Как только вы отключите автоматическую фиксацию, вы получите поведение адаптера базы данных по умолчанию, и Django вам не поможет. Хотя такое поведение указано в PEP 249 , реализации адаптеров не всегда согласуются друг с другом. Внимательно ознакомьтесь с документацией на адаптер, который вы используете.

Вы должны убедиться, что ни одна транзакция не активна, обычно с помощью символа a commit()или a rollback(), прежде чем снова включить автоматическую фиксацию.

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

Транзакции

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

Django не предоставляет API для запуска транзакции. Ожидаемый способ начать транзакцию - отключить автоматическую фиксацию с помощью set_autocommit().

После того, как вы участвуете в транзакции, вы можете либо применить изменения, которые вы выполняли до этого момента commit(), либо отменить их с помощью rollback(). Эти функции определены в django.db.transaction.

commit( используя = None )
rollback( используя = 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( используя = None )

Создает новую точку сохранения. Это отмечает точку транзакции, которая, как известно, находится в «хорошем» состоянии. Возвращает идентификатор точки сохранения ( sid).

savepoint_commit( sid , используя = None )

Освобождает точку сохранения sid. Изменения, внесенные с момента создания точки сохранения, становятся частью транзакции.

savepoint_rollback( sid , используя = None )

Откатывает транзакцию до точки сохранения sid.

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

Кроме того, есть служебная функция:

clean_savepoints( используя = 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( используя = None )
set_rollback( откат , используя = None )

Установка флага отката для Trueпринудительного отката при выходе из самого внутреннего атомарного блока. Это может быть полезно для запуска отката без создания исключения.

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

Примечания к базе данных

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

Хотя SQLite поддерживает точки сохранения, недостаток в конструкции sqlite3 модуля делает их труднодоступными.

Когда автоматическая фиксация включена, точки сохранения не имеют смысла. Когда он отключен, sqlite3неявно фиксируется перед операторами точки сохранения. (На самом деле, оно совершает перед любым заявлением, кроме SELECT, INSERT, UPDATE, DELETEи REPLACE.) Эта ошибка имеет два последствия:

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

Транзакции в MySQL

Если вы используете MySQL, ваши таблицы могут поддерживать или не поддерживать транзакции; это зависит от вашей версии MySQL и типов таблиц, которые вы используете. (Под «типами таблиц» мы подразумеваем что-то вроде «InnoDB» или «MyISAM».) Особенности транзакций MySQL выходят за рамки этой статьи, но на сайте MySQL есть информация о транзакциях MySQL .

Если ваша установка MySQL не поддерживает транзакции, то Django всегда будет работать в режиме автоматической фиксации: операторы будут выполняться и фиксироваться, как только они вызываются. Если ваша установка MySQL делает поддержку транзакций, Django будет обрабатывать транзакции , как описано в данном документе.

Обработка исключений в транзакциях PostgreSQL

Примечание

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

Внутри транзакции, когда вызов курсора PostgreSQL вызывает исключение (обычно IntegrityError), все последующие SQL в той же транзакции завершатся ошибкой с ошибкой «текущая транзакция прервана, запросы игнорируются до конца блока транзакции». Хотя базовое использование save()вряд ли вызовет исключение в PostgreSQL, существуют более сложные шаблоны использования, которые могут, например, сохранение объектов с уникальными полями, сохранение с использованием флага force_insert / force_update или вызов пользовательского SQL.

Есть несколько способов исправить ошибку такого рода.

Откат транзакции

Первый вариант - откатить всю транзакцию. Например:

a.save() # Succeeds, but may be undone by transaction rollback
try:
    b.save() # Could throw exception
except IntegrityError:
    transaction.rollback()
c.save() # Succeeds, but a.save() may have been undone

Вызов transaction.rollback()откатывает всю транзакцию. Любые незавершенные операции с базой данных будут потеряны. В этом примере изменения, внесенные с помощью a.save(), будут потеряны, даже если эта операция сама по себе не вызвала ошибок.

Откат точки сохранения

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

a.save() # Succeeds, and never undone by savepoint rollback
sid = transaction.savepoint()
try:
    b.save() # Could throw exception
    transaction.savepoint_commit(sid)
except IntegrityError:
    transaction.savepoint_rollback(sid)
c.save() # Succeeds, and a.save() is never undone

В этом примере действие a.save()не будет отменено в случае b.save()возникновения исключения.

Copyright ©2021 All rights reserved