Справжніх вкладених транзакцій у PostgreSQL немає. Натомість є точки збереження (savepoints) - мітки всередині транзакції, до яких можна відкотити частину змін, не скасовуючи все.
BEGIN;
INSERT INTO orders (id, total) VALUES (1, 500);
SAVEPOINT before_bonus;
INSERT INTO bonuses (order_id, amount) VALUES (1, 50); -- помилка: порушено обмеження
ROLLBACK TO SAVEPOINT before_bonus; -- скасовано лише бонус
COMMIT; -- замовлення збережено
Навіщо:
- Обробити помилку й продовжити. У PostgreSQL будь-яка помилка переводить транзакцію в стан «aborted» - наступні команди відхиляються до
ROLLBACK. Savepoint дозволяє відкотитися до точки й піти далі. - Масовий імпорт з пропуском поганих рядків - savepoint перед кожним рядком (або порцією).
- Вкладені виклики коду, кожен з яких «хоче транзакцію».
Як це використовує Laravel: вкладений DB::transaction() усередині іншої транзакції створює savepoint, а не новий BEGIN:
DB::transaction(function () {
Order::create([...]);
try {
DB::transaction(fn () => Bonus::create([...])); // SAVEPOINT trans2
} catch (QueryException $e) {
// відкотилося лише створення бонусу
}
});
Підводні камені:
COMMITsavepoint'а нічого не фіксує: зміни всередині стануть постійними лише зCOMMITзовнішньої транзакції. Якщо вона відкотиться, відкотиться все.- Ціна: кожен savepoint споживає ресурси. Тисячі savepoint'ів в одній транзакції (по одному на рядок) помітно сповільнюють роботу - для масового імпорту краще перевіряти дані до вставки чи використовувати
ON CONFLICT. - Побічні ефекти поза базою (листи, HTTP-запити) відкат savepoint'а не скасує.