Menu

SAVEPOINT в SQLite: вложенные транзакции и ROLLBACK TO

Как работают точки сохранения в SQLite — именованные метки внутри транзакции, к которым можно откатиться, не теряя всю транзакцию целиком.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

SAVEPOINT в SQLite — это именованная закладка внутри транзакции

Обычная транзакция работает по принципу «всё или ничего»: всё, что находится между BEGIN и COMMIT, либо коммитится целиком, либо целиком откатывается. Чаще всего это именно то, что нужно, — но иногда хочется большей гибкости. Что-то вроде: «Попробуй применить вот эту порцию изменений; если что-то пойдёт не так — откати только её, а остальную транзакцию оставь в живых».

Именно для этого и нужны точки сохранения SQLite. Ставите именованную закладку, делаете нужные изменения, а дальше либо фиксируете их (RELEASE), либо отматываете обратно к закладке (ROLLBACK TO).

Списание у Ады и зачисление у Бориса остались в базе. Ошибочное обновление строки Nobody откатилось, а остальная часть транзакции уцелела.

Три команды

Весь API — это три инструкции:

  • SAVEPOINT name — поставить закладку.
  • RELEASE SAVEPOINT name — зафиксировать всё, что сделано после закладки, и убрать саму закладку.
  • ROLLBACK TO SAVEPOINT name — откатить все изменения, сделанные после закладки; сама закладка остаётся, так что можно попробовать ещё раз.

Слово SAVEPOINT после RELEASE и ROLLBACK TO указывать необязательно — RELEASE risky и ROLLBACK TO risky сработают точно так же.

Строка попытка шага 2 для итоговой базы как будто никогда и не существовала. Всё остальное успешно сохраняется.

SAVEPOINT без внешней транзакции

И вот небольшой нюанс: команду SAVEPOINT можно выполнить и без предварительного BEGIN. SQLite в этом случае молча откроет транзакцию сам, а самая внешняя точка сохранения возьмёт на себя роль самой транзакции. RELEASE для такой точки выполнит коммит, а ROLLBACK TO откатит изменения без фиксации.

Именно поэтому точки сохранения иногда называют «именованными транзакциями». Но смешивать оба стиля в реальном коде — верный путь к путанице, так что лучше выбрать что-то одно. На практике чаще всего внешнюю границу транзакции задают через явные BEGIN ... COMMIT, а SAVEPOINT используют только как внутренние точки для частичного отката.

Вложенные точки сохранения в SQLite

Точки сохранения работают как стек. Можно поставить одну внутри другой и откатить вложенную, не трогая внешнюю:

В итоге в таблице остались: a, b, d. Откат к точке сохранения inner убрал c, но всё, что было сделано до inner (вставка b), уцелело, а сама транзакция продолжила работать.

Откат к внешней точке сохранения отменяет и всё, что происходило на внутренних уровнях, — вся часть стека выше указанного имени схлопывается за один раз:

Записи b и c исчезли. ROLLBACK TO outer откатывает всё, что произошло после установки outer, — в том числе inner и вставку c.

Зачем нужны точки сохранения SQLite

Классический сценарий — пакетная обработка, когда отдельные элементы могут падать с ошибкой, но это не должно ломать всю пачку. Оборачиваем каждый элемент в SAVEPOINT: если что-то пошло не так, откатываемся к точке сохранения и спокойно идём дальше:

В реальном коде «плохой» INSERT выбросит ошибку, приложение её поймает, выполнит ROLLBACK TO и продолжит работу. Две корректные строки попадут в базу, а проблемная не испортит весь пакет.

По такому же принципу ORM и инструменты миграций реализуют вложенные транзакции SQLite — они не вкладывают BEGIN друг в друга (SQLite этого не позволяет), а сопоставляют вложенные вызовы с точками сохранения.

На что стоит обратить внимание

Несколько нюансов, на которых спотыкаются те, кто только начинает работать с SAVEPOINT:

  • COMMIT всегда фиксирует транзакцию целиком. Сколько бы открытых точек сохранения у вас ни было — COMMIT (или его синоним END) закрывает всю внешнюю транзакцию. Не воспринимайте RELEASE как «частичный коммит»: пока окружающая транзакция не зафиксирована, ничего на диск не записывается.
  • ROLLBACK без TO отменяет всё. Он завершает транзакцию и сбрасывает все открытые точки сохранения. Если хотите сохранить транзакцию живой — используйте ROLLBACK TO имя.
  • Точка сохранения живёт, пока её не освободят или не откатят. Забыли сделать RELEASE — данные не пропадут, просто «закладка» будет висеть до конца транзакции.
  • Имена не обязаны быть уникальными. Если объявить SAVEPOINT s дважды, то ROLLBACK TO s откатится к самой последней. Удобно для рекурсии и сбивает с толку, если так получилось случайно.

Дальше: представления (views)

Точки сохранения дают тонкий контроль над записью. Следующий шаг — управлять тем, как вы читаете данные: сохранить запрос как именованный переиспользуемый объект, к которому можно обращаться через SELECT, как к обычной таблице. Это и есть представление (view), и о нём — в следующей главе.

Часто задаваемые вопросы

Что такое SAVEPOINT в SQLite?

SAVEPOINT — это именованная метка, которую вы ставите внутри транзакции. Позже можно сделать ROLLBACK TO с этим именем, чтобы откатить всё, что произошло после метки, либо RELEASE, чтобы зафиксировать изменения и убрать саму метку. По сути, savepoint позволяет разбить транзакцию на более мелкие куски, которые можно откатывать по отдельности.

Чем SAVEPOINT отличается от обычной транзакции?

Транзакция начинается с BEGIN и завершается через COMMIT или ROLLBACK. SAVEPOINT же ставится внутри уже открытой транзакции командой SAVEPOINT имя и даёт точку частичного отката. Откат до savepoint не закрывает внешнюю транзакцию — вы спокойно продолжаете работу и коммитите её позже.

Можно ли вкладывать SAVEPOINT друг в друга?

Да, и это нормальная практика. Точки сохранения складываются в стек, и ROLLBACK TO outer откатывает всё до этого уровня, включая все вложенные savepoint'ы внутри. Имена при этом могут повторяться — SQLite выберет самый свежий savepoint с этим именем, как и положено стеку.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ