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

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

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

Обычно Django выполняет создаёт транзакцию и фиксирует её автоматически после выполнения любой встроенной, изменяющей данный функции модели. Например, если вызвать model.save() или model.delete(), то изменения будут немедленно зафиксированы в базе данных.

Такое поведение аналогично автоматической фиксации (auto-commit), которая предоставляется большинством баз данных. Как только вы выполняете действие, которое требует записи в базу данных, Django выполняет INSERT/UPDATE/DELETE операторы и затем фиксирует изменения с помощью COMMIT.

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

Рекомендованным способом использования транзакций в веб запросах является их привязка к фазам запроса и ответа с помощью прослойки (middleware) Django TransactionMiddleware.

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

Для активации этой возможности, просто добавьте прослойку TransactionMiddleware в настройку MIDDLEWARE_CLASSES:

MIDDLEWARE_CLASSES = (
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.transaction.TransactionMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',
)

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

Различные кэширующие прослойки не попадают под это правило: CacheMiddleware, UpdateCacheMiddleware, и FetchFromCacheMiddleware. Даже при использовании кэширования в базу данных, бэкэнд кэша использует для своей работы отдельный курсор БД.

Примечание

The TransactionMiddleware only affects the database aliased as “default” within your DATABASES setting. If you are using multiple databases and want transaction control over databases other than “default”, you will need to write your own transaction middleware.

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

Для большинства людей неявные автоматические транзакции работают отлично. Тем не менее, если вам требуется более тонкий контроль над управлением, вы можете воспользоваться рядом функций из django.db.transaction на уровне функции или блока кода.

Эти функции, рассмотренные подробно далее, могут быть использованы двумя способами:

  • В качестве декоратора функции. Например:

    from django.db import transaction
    
    @transaction.commit_on_success
    def viewfunc(request):
        # ...
        # this code executes inside a transaction
        # ...
    
  • В качестве менеджера контекста вокруг блока кода:

    from django.db import transaction
    
    def viewfunc(request):
        # ...
        # this code executes using default transaction management
        # ...
    
        with transaction.commit_on_success():
            # ...
            # this code executes inside a transaction
            # ...
    

Оба способа работают со всеми поддерживаемыми версиями Python.

Для наибольшей совместимости, все приведённые далее примеры показывают использование декоратора для управления транзакциями. Но все эти функции могут быть использованы и с контекстным менеджером.

Примечание

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

autocommit()

Используйте декоратор autocommit для принудительного использования стандартного механизма Django для автоматической фиксации изменений, вне зависимости от глобальной настройки управления транзакциями.

Пример:

from django.db import transaction

@transaction.autocommit
def viewfunc(request):
    ....

@transaction.autocommit(using="my_other_database")
def viewfunc2(request):
    ....

В функции viewfunc() транзакции будут зафиксированы как только вы вызовете model.save(), model.delete() или любую другую функцию, которая приводит к записи в базу данных. Функция viewfunc2() работает аналогично, но использует соединение "my_other_database".

commit_on_success()

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

from django.db import transaction

@transaction.commit_on_success
def viewfunc(request):
    ....

@transaction.commit_on_success(using="my_other_database")
def viewfunc2(request):
    ....

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

commit_manually()

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

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

Ручное управление транзакцией выглядит следующим образом:

from django.db import transaction

@transaction.commit_manually
def viewfunc(request):
    ...
    # You can commit/rollback however and whenever you want
    transaction.commit()
    ...

    # But you've got to remember to do it yourself!
    try:
        ...
    except:
        transaction.rollback()
    else:
        transaction.commit()

@transaction.commit_manually(using="my_other_database")
def viewfunc2(request):
    ....

Требования для обработки транзакций

Django требует, чтобы каждая открытая транзакция была закрыта до завершения обработки запроса. Если вы используете autocommit() (стандартный режим фиксации) или commit_on_success(), то фиксация будет произведена автоматически(за исключением выполенения собственных SQL запросов). При ручном управлении транзакциями (при использовании декоратора commit_manually()), вы должны обеспечить фиксацию или откат изменений до завершения обработки запроса.

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

Как глобально деактивировать управление транзакциями

Сторонники параноидального контроля могут полностью отключить всё управление транзакциями, установив параметр TRANSACTIONS_MANAGED равным True в файле конфигурации Django.

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

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

Промежуточные точки

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

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

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

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

transaction.savepoint(using=None)

Создаёт новую промежуточную точку, обозначая точку в транзакции, отмечающую “хорошее” состояние.

Возвращает идентификатор промежуточной точки (sid).

transaction.savepoint_commit(sid, using=None)

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

transaction.savepoint_rollback(sid, using=None)

Откатывает транзакцию до последней точки.

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

from django.db import transaction

@transaction.commit_manually
def viewfunc(request):

  a.save()
  # open transaction now contains a.save()
  sid = transaction.savepoint()

  b.save()
  # open 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()

  transaction.commit()

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

При использовании MySQL поддержка трензакция зависит от версии движка базы данных и от используемого типа таблиц. (Под “типом таблицы” мы подразумеваем “InnoDB” или “MyISAM”.) Особенности транзакций MySQL выходят за рамки данной статьи, но сайт MySQL содержит информацию о транзакциях.

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

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

Когда вызов к курсору 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() будут потеряны, несмотря на то, что эта операция прошла без ошибок.

Откат до промежуточной точки

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

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() вызовет исключение.

Автоматическая фиксация на уровне базы данных

Начиная с PostgreSQL 8.2 появилась возможность использовать автоматическую фиксацию на уровне базы данных. Если вы используете эту опцию, то для вам нет постоянно открытой транзакции, таким образом всегда можно продолжить работу после обработки исключения. Например:

a.save() # succeeds
try:
    b.save() # Could throw exception
except IntegrityError:
    pass
c.save() # succeeds

Примечание

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