Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Транзакции Python: Django ORM

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

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

Тут стоит сразу отметить, что по умолчанию ORM использует режим "autocommit" и все запросы сразу применяются к БД. Об этой особенности стоит помнить и явно использовать транзакции в тех местах, где консистентность может быть нарушена.

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

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

from django.db import transaction


with transaction.atomic():
   # весь код в этом блоке выполнится в рамках транзакции
   do_more_stuff()

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

Разработчики на Django часто хотят выполнить в транзакции весь код какой-либо view. При этом atomic() используют как декоратор:

from django.db import transaction


@transaction.atomic
def viewfunc(request):
    # Вся view работает в рамках транзакции
    do_stuff()

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

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

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

Функция savepoint() из модуля django.db.transaction создает точку сохранения, возвращая её идентификатор ("savepoint id" или "sid").

Функция savepoint_commit(sid) сохраняет изменения, произошедшие с момента создания соответствующей savepoint. А функция savepoint_rollback(sid) откатывает изменения к тому состоянию, которые имела база на момент создания соответствующей savepoint. Тот факт, что при вызове упомянутых двух функций указывается "sid", говорит о том, что откатывать изменения можно к любой из ранее созданных точек сохранения (в пределах транзакции).

Вложенные транзакции

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

Ограничения

Транзакции поддерживаются абсолютным большинством СУБД, среди тех, с которыми Django умеет работать. Но этого же нельзя сказать о savepoints. Если конкретная СУБД в принципе не поддерживает точки сохранения, то любой код, который их использует, будет сразу вносить изменения в БД, а откат работать перестанет. И если при явном использовании savepoint() и прочих специфичных функций программист обычно знает о том, что делает, то использование вложенных вызовов atomic() может неприятно удивить, если забыть об отсутствии savepoints в конкретной СУБД.

Из сказанного выше стоит сделать следующий вывод: один уровень транзакций работает везде и всегда (практически), а все виды вложенности требуют внимательного отслеживания того, с какими СУБД будет работать проект.


Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

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

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
Обучитесь разработке бэкенда сайтов и веб-приложений — серверной части, которая отвечает за логику и базы данных
10 месяцев
с нуля
Старт 28 ноября

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»