• 3.1
  • 5.0
  • 6.1
  • Версия документации: 3.2

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

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

Управление транзакциями

Стандартное поведение Django

По умолчанию Django использует режим автоматической фиксации(«autocommit»). Каждый запрос сразу фиксируется в базе данных, если не используется транзакция. Смотрите ниже.

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

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

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

Обычной практикой для Web-приложений является оборачивание каждого запроса в транзакцию. Чтобы активировать такое поведение, установите 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, durable=False)

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

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

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

atomic может использоваться как decorator:

from django.db import transaction

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

и как context manager:

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 будет зависеть от вашей базы данных.

You may need to manually revert model state when rolling back a transaction.

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

Например, возьмем 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 блокирует некоторые функции. При попытке явно зафиксировать или отменить изменения, или изменить статус «autocommit» подключения к базе данных внутри блока atomic, будет вызвано исключение.

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

Код обработки транзакций в Django выполняет следующие действия:

  • создает транзакцию при входе в блок atomic;

  • создает точку сохранения при входе во вложенный блок atomic;

  • сохраняет или отменяет точку сохранения при выходе из вложенного блока;

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

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

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

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

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

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

django.test.TestCase disables the durability check to allow testing durable atomic blocks in a transaction for performance reasons. Use django.test.TransactionTestCase for testing durability.

Changed in Django 3.2:

The durable argument was added.

Autocommit

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

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

Это не всегда удобно при разработке приложения. Чтобы решить эту проблему, большинство баз данных поддерживает режим автоматической фиксации(autocommit). При включенном «autocommit», если транзакция не активна, каждый SQL запрос обернут в транзакцию. То есть транзакция для каждого запроса не только создается, но и автоматически фиксируется, если запросы был успешно выполнен.

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

Вы можете отключить такое поведение, но мы не рекомендует этого делать.

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

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

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

Выполнение операция после коммита

Sometimes you need to perform an action related to the current database transaction, but only if the transaction successfully commits. Examples might include a Celery task, an email notification, or a cache invalidation.

Django provides the on_commit() function to register callback functions that should be executed after a transaction is successfully committed:

on_commit(func, using=None)

Pass any function (that takes no arguments) to 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)

You can also wrap your function in a lambda:

transaction.on_commit(lambda: some_celery_task.delay('arg1'))

The function you pass in will be called immediately after a hypothetical database write made where on_commit() is called would be successfully committed.

If you call on_commit() while there isn’t an active transaction, the callback will be executed immediately.

If that hypothetical database write is instead rolled back (typically when an unhandled exception is raised in an atomic() block), your function will be discarded and never called.

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

Точки сохранения (например вложенные блоки 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()

Порядок выполнения

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

Обработка исключений

If one on-commit function within a given transaction raises an uncaught exception, no later registered functions in that same transaction will run. This is the same behavior as if you’d executed the functions sequentially yourself without on_commit().

Время выполнения обработчиков

Your callbacks are executed after a successful commit, so a failure in a callback will not cause the transaction to roll back. They are executed conditionally upon the success of the transaction, but they are not part of the transaction. For the intended use cases (mail notifications, Celery tasks, etc.), this should be fine. If it’s not (if your follow-up action is so critical that its failure should mean the failure of the transaction itself), then you don’t want to use the on_commit() hook. Instead, you may want two-phase commit such as the psycopg Two-Phase Commit protocol support and the optional Two-Phase Commit Extensions in the Python DB-API specification.

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

Если назначить обработчик в «autocommit» режиме вне блока atomic(), он будет выполнен моментально, а не после коммита.

Обработчики коммита транзакции работают только с «autocommit» режимом и atomic() (или при ATOMIC_REQUESTS) API транзакций. Вызвав on_commit() при отключенном «autocommit» и вне блока atomic приведет к ошибке.

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

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

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

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

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

Обработчик отмены транзакции реализовать на много сложнее т.к. причин отката на много больше.

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

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

Низкоуровневый API

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

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

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

Autocommit

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

get_autocommit(using=None)
set_autocommit(autocommit, using=None)

Каждая из этих функций принимает аргумент using, который обозначает имя базы данных. Если этот аргумент не указан, то все действия идут относительно стандартной ("default") базы данных.

«autocommit» по умолчанию включен. Если вы выключили его, на вашей ответственности и его включение.

При выключении «autocommit», вы получаете поведение по умолчанию библиотеки, которая используется для работы с базой данных, и Django ничем вам не поможет. Это поведение описано в PEP 249, но реализация в различных библиотеках может отличаться. Вам следует изучить документацию используемой библиотеки.

Перед включением «autocommit» вы должны убедиться, что транзакция не активна, обычно выполнив commit() или rollback().

Django не позволит выключить «autocommit», если активен блок atomic(), т.к. это нарушит атомарность.

Транзакции

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

Django не предоставляет API для явного создания транзакции. Чтобы создать транзакцию, отключите «autocommit» с помощью set_autocommit().

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

commit(using=None)
rollback(using=None)

Каждая из этих функций принимает аргумент using, который обозначает имя базы данных. Если этот аргумент не указан, то все действия идут относительно стандартной ("default") базы данных.

Django не позволит зафиксировать или отменить изменения, если блок atomic() активен, т.к. это нарушит атомарность.

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

Точкой сохранения называют маркер внутри транзакции, который позволяет вам отменить лишь часть транзакции, а не всю. Точки сохранения доступны при использовании бэкендов SQLite (≥ 3.6.8), PostgreSQL 8, Oracle и MySQL(при использовании InnoDB). Все остальные бэкенды предоставляют функции для создания точек сохранения, но они ничего не делают.

Точки сохранения бесполезны, если вы используете стандартное поведение Django – «autocommit». Тем не менее, при создании транзакции через atomic(), каждая открытая транзакция выполняет ряд операций в базе данных, ожидая фиксации или отката транзакции. Если вы выполните откат транзакции, то будет выполнен откат всей транзакции. Точки сохранения предоставляют возможность выполнять частичный откат, вместо выполнения полного отката, который делается с помощью transaction.rollback().

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

Каждая из этих функция принимает аргумент using, который обозначает имя базы данных с которой производится работа. Если этот аргумент не указан, то все действия идут относительно стандартной ("default") базы данных.

Точки сохранения управляются тремя функциями из django.db.transaction:

savepoint(using=None)

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

savepoint_commit(sid, using=None)

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

savepoint_rollback(sid, using=None)

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

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

Также Django предоставляет несколько дополнительных функций:

clean_savepoints(using=None)

Сбрасывает счетчик генератора ID точек сохранения.

Следующие примеры демонстрируют использование промежуточных точек:

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

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

Заметки о различных базах данных

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

Хотя SQLite ≥ 3.6.8 и поддерживает точки сохранения, из-за особенностей работы sqlite3 их сложно использовать.

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

  • The low level APIs for savepoints are only usable inside a transaction ie. inside an atomic() block.

  • Невозможно использовать atomic() при выключенном «autocommit».

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

При использовании MySQL поддержка транзакция зависит от версии движка базы данных и от используемого типа таблиц. (Под «типом таблицы» мы подразумеваем «InnoDB» или «MyISAM».) Особенности транзакций MySQL выходят за рамки данной статьи, но сайт MySQL содержит информацию о `транзакциях<http://dev.mysql.com/doc/refman/5.6/en/sql-syntax-transactions.html>.

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

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

Примечание

Этот раздел важен только при создании собственного управления транзакциями. Описанная проблема не возникнет при стандартном поведении Django и при использовании atomic().

Inside a transaction, when a call to a PostgreSQL cursor raises an exception (typically IntegrityError), all subsequent SQL in the same transaction will fail with the error «current transaction is aborted, queries ignored until end of transaction block». While the basic use of save() is unlikely to raise an exception in PostgreSQL, there are more advanced usage patterns which might, such as saving objects with unique fields, saving using the force_insert/force_update flag, or invoking custom 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() вызовет исключение.

Back to Top