Savepoint: контрольные точки внутри транзакции
Внутренние точки сохранения: зачем и когда? 🎯
Транзакции — это атомарные операции, но иногда нужно больше гибкости внутри них. Savepoint — это "чекпоинт" внутри транзакции, позволяющий откатиться не полностью, а до определённой точки.
👉 Где применяется:
- Сложные миграции данных с промежуточными этапами
- Многошаговые операции с возможностью частичного отката
- Тестирование подзапросов без завершения транзакции
BEGIN; -- Начало транзакции
INSERT INTO users (name) VALUES ('Alice');
SAVEPOINT first_user; -- Создали точку сохранения!
INSERT INTO users (name) VALUES ('Bob');
-- Ой, что-то пошло не так...
ROLLBACK TO first_user; -- Откатились до точки, Bob не добавился
COMMIT; -- Фиксируем только Alice
Синтаксис Savepoint: просто как 1-2-3 🧩
Создание точки сохранения
SAVEPOINT имя_точки; -- Без кавычек, регистр зависит от СУБД
Возврат к точке
ROLLBACK TO [SAVEPOINT] имя_точки; -- Ключевое слово SAVEPOINT опционально
Удаление точки
RELEASE SAVEPOINT имя_точки; -- Освобождает ресурсы
❗ Важно: В PostgreSQL точки автоматически удаляются при COMMIT/ROLLBACK всей транзакции.
Реальный кейс: обработка заказов с резервированием 🛒
Представьте интернет-магазин, где нужно:
- Зарезервировать товар
- Создать заказ
- Списать деньги
- Обновить статистику
При ошибке на шаге 3 нужно отменить списание, но сохранить заказ в логе.
BEGIN;
-- 1. Резервируем товар
UPDATE products SET stock = stock - 1 WHERE id = 123;
SAVEPOINT stock_updated;
-- 2. Создаём заказ
INSERT INTO orders (product_id, user_id) VALUES (123, 456);
SAVEPOINT order_created;
-- 3. Пытаемся списать деньги (но вдруг ошибка?)
-- Предположим, тут произошла ошибка платежа
ROLLBACK TO order_created; -- Отменяем только списание
-- 4. Фиксируем остальные изменения
COMMIT;
Глубокое погружение: как это работает под капотом 🔍
- Хранение: Savepoints записываются в специальную область памяти транзакции.
- Вложенность: Можно создавать точки внутри точек (как матрёшка).
- Производительность: Каждый SAVEPOINT — небольшой оверхед, но дешевле полного ROLLBACK.
BEGIN;
SAVEPOINT A;
INSERT INTO table1 VALUES (1);
SAVEPOINT B;
INSERT INTO table1 VALUES (2);
ROLLBACK TO B; -- Удалится только значение 2
INSERT INTO table1 VALUES (3);
COMMIT; -- В таблице будут 1 и 3
Ограничения и особенности 🚧
- Не все СУБД поддерживают: MySQL, PostgreSQL, Oracle — да; SQLite — с ограничениями.
- Вложенность: В PostgreSQL до 64 уровней.
- Именование: Некоторые системы требуют уникальных имён в пределах транзакции.
-- Пример для MySQL
START TRANSACTION;
SAVEPOINT point_a;
SAVEPOINT point_a; -- Ошибка: дублирование имени
Практическое задание 🛠️
Создайте транзакцию для перевода денег между счетами:
- Начало транзакции
- Проверка баланса (точка сохранения)
- Списание со счёта А
- Зачисление на счёт Б
- Если ошибка на шаге 4 — откат до проверки баланса