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

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

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

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

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

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

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

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

В предыдущих версиях Django обработка транзакций была сложнее.

Привязка транзакций к 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 1.6:

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

Явное управление транзакциями

Добавлено в Django 1.6.

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()

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

Избегайте перехвата ошибок в atomic!

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Autocommit

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

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

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

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

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

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

До Django 1.6 “autocommit” был выключен, и эмулировался явным фиксированием изменений после каждой операции записи ORM.

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

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

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

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

Ранее управлялось через настройку TRANSACTIONS_MANAGED.

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

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

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

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

Autocommit

Добавлено в Django 1.6.

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().

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

При вложенных блоках 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()
Добавлено в Django 1.6.

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

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

  • Невозможно использовать 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().

В транзакции, когда вызов к курсору 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
try:
    sid = transaction.savepoint()
    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 1.5 и ранее

Функционал, описанный ниже, устарел в Django 1.6 и будет удален в Django 1.8. Он описан в этом разделе, чтобы упростить процесс обновления до нового API управления транзакциями.

Устаревший API

Следующие функции, определенные в django.db.transaction, позволяют управлять транзакциями на уровне функции или блока кода. Они могут использоваться как декораторы или менеджеры контекста, и принимают аргумент using, как и atomic().

autocommit()

Включает стандартное поведение автоматический фиксации в Django

Транзакции будут зафиксированы как только вы вызовете model.save(), model.delete() или любую другую функцию, которая приводит к записи в базу данных.

commit_on_success()

Использует единую транзакцию для всех действий в функции.

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

commit_manually()

Указывает Django, что вы самостоятельно управляете транзакцией.

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

Состояния транзакции

Три функции, описанные выше, используют концепцию, которая называется “состояние транзакции”. Этот механизм устарел в Django 1.6, но все еще доступен до Django 1.8.

В любой момент времени каждое подключение к базе данных находится в одном из двух состояний:

  • автоматический режим: автоматическая фиксация включена;

  • ручной режим: автоматическая фиксация выключена.

Django запускается в автоматическом режиме. TransactionMiddleware, commit_on_success() и commit_manually() активируют ручной режим; autocommit() активирует автоматический режим.

Внутри Django содержит стек состояний. Включения и отключения должны быть уравновешены.

Например, commit_on_success() при входе в контролируемый блок кода активирует ручной режим, при выходе из блока – фиксирует или отменяет изменения и активирует ручной режим.

Таким образом, commit_on_success() на самом деле выполняет два действия: изменяет состояния транзакции и создает блок транзакции. Вложенность приведет к ожидаемому результату в контексте состояний транзакции, но не с точки зрения семантики транзакций. В большинстве случаев вложенный блок выполнит фиксацию изменений, нарушив атомарность внешнего блока.

autocommit() и commit_manually() имеют аналогичные ограничения.

Изменения в API

Промежуточный слой(middleware) транзакций

В Django 1.6 TransactionMiddleware устарел и заменен на ATOMIC_REQUESTS. Хотя общее поведение не изменилось, существует два отличия.

Предыдущий API позволял переключить режим автоматической фиксации, или явно выполнить фиксацию изменений в представлении. Т.к. ATOMIC_REQUESTS использует atomic(), которая обеспечивает атомарность, теперь это не доступно. Однако, на более высоком уровне вы можете исключить создание транзакции для представления. Для этого оберните представление декоратором non_atomic_requests() вместо autocommit().

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

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

Начиная с Django 1.6 atomic() единственный поддерживаемый API для создания транзакций. В отличии от старого API, эта функция поддерживает вложенность и всегда гарантирует атомарность.

В большинстве случаев эта функция просто заменит commit_on_success().

Пока старый API не будет удален, вы можете использовать atomic() вместе с autocommit(), commit_on_success() или commit_manually(). Однако, обратное не работает, т.к. вложенные старые декораторы и менеджеры контекста нарушат атомарность.

Управление автоматической фиксацией

Django 1.6 предоставляет API для управления автоматической фиксацией изменений.

Чтобы временно отключить автоматическую фиксацию, вместо:

with transaction.commit_manually():
    # do stuff

теперь следует использовать:

transaction.set_autocommit(False)
try:
    # do stuff
finally:
    transaction.set_autocommit(True)

Чтобы временно включить автоматическую фиксацию, используйте:

with transaction.autocommit():
    # do stuff

теперь следует использовать:

transaction.set_autocommit(True)
try:
    # do stuff
finally:
    transaction.set_autocommit(False)

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

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

Вместо настройки TRANSACTIONS_MANAGED = True, установите ключ AUTOCOMMIT в False для соответствующей настройки базы данных, как это указанно в Отключение управления транзакциями.

Несовместимые изменения

Начиная с 1.6 Django использует “autocommit” на уровне базы данных. Ранее использовался “autocommit” на уровне приложения, вызывая фиксирование изменения при операциях записи ORM.

В результате каждый запрос к базе данных (например, запрос записи в ORM) начинает транзакцию, которая длится до операции записи ORM. Такие “автоматические транзакции” больше не существуют в Django 1.6.

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

Обратите внимание, это не относится к ручному режиму. Этот раздел подразумевает автоматический режим. Смотрите описание режимов выше.

Последовательность собственных SQL запросов

Теперь, если вы выполняете несколько собственных SQL запросов подряд, каждый из них будет в собственной транзакции, ранее они выполнялись в одной “автоматической транзакции”. Для старого поведения, чтобы заключить последовательность таких запросов в атомарный блок, используйте atomic().

Чтобы определить эту проблему, ищите вызов cursor.execute(). Обычно перед ним используется transaction.commit_unless_managed(), который теперь можно удалить.

Запрос выборки для обновления

Если вы полагались на “автоматическую транзакцию”, которая обеспечивает блокирование между select_for_update() и последующей операцией записи — чрезвычайно плохой подход, но все же возможен — вам следует обернуть соответствующий код в atomic(). С Django 1.6.3 выполнение запроса с select_for_update() в режиме автоматический фиксации вызовет исключение TransactionManagementError.

Использование уровней изоляции транзакций

Если вы используете “repeatable read” уровень изоляции или выше, и вы предполагаете, что “автоматическая транзакция” обеспечит согласованность нескольких последовательных запросов на чтение, новое поведение транзакций может нарушить эту логику. Чтобы явно обеспечить согласованность последовательности запросов на чтение, используйте atomic().

MySQL по умолчанию использует “repeatable read”, а SQLite “serializable”. При использовании этих баз данных можно столкнуться с такой проблемой.

При уровне изоляции “read committed” и ниже, “автоматические транзакции” не влияют на работу последовательных операций ORM.

PostgreSQL и Oracle по умолчанию используют “read committed” и не подвержены этой проблеме, если только вы не меняли уровень изоляции.

Использование не поддерживаемых функций базы данных

При использовании тригеров, представлений(VIEW), или функций в базе данных, операции чтения ORM могут вызывать операции изменения на уровне базы данных. В Django 1.5 и ниже эти ситуации никак не обрабатывались. Теоретически это может привести к неоднозначному поведению в Django 1.6 и выше. В случае сомнений используйте atomic().