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

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

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

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

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

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

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

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

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

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

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

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

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

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

Когда представление возвращает поток response ( 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 можно использовать как декоратор :

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 , спецификация 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() .

Порядок исполнения

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

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

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

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

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

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

Почему нет точки входа для отмененных транзакций?

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

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

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

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

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

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

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

Автокоммит

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

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

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

Автоматическая фиксация («autocommit») изначально включена. Если вы отключите его, вы обязаны восстановить его позже.

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

Вы должны убедиться, что транзакции не ожидают выполнения, обычно путем вызова commit() или 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() , откат всего блока все равно будет, потому что Django не знает, что вы справились с ситуацией на более низком уровне! Чтобы предотвратить это, вы можете контролировать поведение отмены с помощью следующих функций.

get_rollback( используя = None )
set_rollback( откат , используя = None )

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

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

Примечания, относящиеся к определенным базам данных

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

Хотя точки сохранения поддерживаются 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 ©2020 All rights reserved