Savepoint в PostgreSQL: как отменить часть транзакции

Savepoint в PostgreSQL: как отменить часть транзакции
Содержание статьи

SAVEPOINT создаёт точку внутри открытой транзакции PostgreSQL. Команда ROLLBACK TO SAVEPOINT отменяет изменения после этой точки и позволяет продолжить транзакцию. Изменения до точки остаются, но окончательно сохраняются только при COMMIT.

В упражнении Mentoring.digital используем временную таблицу и вымышленные задачи. Выполняйте команды в отдельном учебном соединении, где нет незавершённой рабочей транзакции.

Создайте точку перед необязательным шагом

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

CREATE TEMP TABLE savepoint_demo (
    id integer PRIMARY KEY,
    title text NOT NULL
);

BEGIN;
INSERT INTO savepoint_demo VALUES (1, 'Основная задача');
SAVEPOINT optional_step;
INSERT INTO savepoint_demo VALUES (2, 'Черновой вариант');
ROLLBACK TO SAVEPOINT optional_step;
INSERT INTO savepoint_demo VALUES (3, 'Исправленный вариант');
RELEASE SAVEPOINT optional_step;
COMMIT;

SELECT id, title FROM savepoint_demo ORDER BY id;

Итоговый SELECT должен вернуть строки 1 и 3. Строка 2 отменена. ROLLBACK TO не завершил всю транзакцию, поэтому после него удалось вставить исправленный вариант и выполнить COMMIT.

Как продолжить после ошибки оператора

Теперь намеренно нарушим первичный ключ. Этот фрагмент выполняйте по одному оператору: некоторые клиенты прекращают исполнение всего скрипта при первой ошибке. После сообщения о дублирующемся ключе отдельно выполните ROLLBACK TO и оставшиеся команды.

BEGIN;
SAVEPOINT before_attempt;
INSERT INTO savepoint_demo VALUES (1, 'Повторный id');
-- Ожидаемая ошибка: id 1 уже существует.
ROLLBACK TO SAVEPOINT before_attempt;
INSERT INTO savepoint_demo VALUES (4, 'Новая задача');
RELEASE SAVEPOINT before_attempt;
COMMIT;

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

RELEASE не означает COMMIT

RELEASE SAVEPOINT удаляет точку и связанные с ней более поздние точки, сохраняя текущие изменения внутри транзакции. Внешний ROLLBACK всё ещё может их отменить. ROLLBACK TO, напротив, отменяет более поздние изменения и точки, но оставляет саму указанную точку доступной.

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

Что частичный откат не гарантирует

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

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

Для закрепления повторите первый пример с полным ROLLBACK вместо COMMIT и сравните результат. Транзакции можно отработать на курсе SQL с наставником, а границы реальной операции обсудить с ментором по SQL.

Похожие статьи по данной теме