Часом транзакції нагадують персонажів супергеройських фільмів. Вони рятують наші бази даних від катастрофи при збоях, помилках чи глюках. Якщо ти працюєш із задачею, де треба виконати кілька операцій, які не можна розділяти, транзакції забезпечать їхнє єдине виконання. Давай розберемо, як це працює на прикладі обробки платежів.
Обробка платежів
Уяви класичну ситуацію: у тебе є два банківських рахунки, і ми хочемо перекинути гроші з одного на інший. Це не просто операція "однією кнопкою". Треба впевнитися, що ми коректно списали гроші з одного рахунку і додали їх на інший. Будь-яка помилка може призвести до катастрофи: або обидва рахунки залишаться незмінними, або зіб’ється баланс (наприклад, гроші зникнуть або з’являться "з повітря").
Сценарій: Переказ грошей між рахунками
Ось наш код. Читай уважно, ніби це послання з далекої галактики:
-- Починаємо транзакцію
BEGIN;
-- Крок 1. Списуємо гроші з акаунта відправника
UPDATE accounts
SET balance = balance - 100
WHERE account_id = 1;
-- Крок 2. Додаємо гроші на акаунт отримувача
UPDATE accounts
SET balance = balance + 100
WHERE account_id = 2;
-- Все пройшло ок? Тоді зберігаємо зміни!
COMMIT;
Що тут важливо?
- Якщо на
Кроці 1абоКроці 2щось пішло не так (наприклад, помилка в запиті, недостатньо коштів), транзакцію можна відкотити назад черезROLLBACK, і дані залишаться у початковому стані. COMMITгарантує, що зміни застосуються тільки якщо ВСІ кроки успішні.
Додаємо перевірку балансу
А що, якщо у відправника недостатньо грошей для переказу? Давай додамо перевірку балансу, щоб випадково не "загнати його в мінус".
-- Починаємо транзакцію
BEGIN;
-- Отримуємо поточний баланс відправника
DO $$
DECLARE
current_balance NUMERIC;
BEGIN
SELECT balance INTO current_balance FROM accounts WHERE account_id = 1;
-- Перевіряємо, чи вистачає грошей
IF current_balance >= 100 THEN
-- Якщо грошей достатньо, виконуємо переказ
UPDATE accounts
SET balance = balance - 100
WHERE account_id = 1;
UPDATE accounts
SET balance = balance + 100
WHERE account_id = 2;
-- Фіксуємо зміни
COMMIT;
ELSE
-- Якщо грошей не вистачає, відкочуємо
ROLLBACK;
RAISE NOTICE 'Недостатньо коштів для переказу!';
END IF;
END $$;
Що тут вже цікавіше?
- Ми юзаємо блок PL/pgSQL з перевіркою умови через
IF. Якщо баланс менший за потрібну суму, транзакція буде відхилена і нічого не зміниться. ROLLBACKскасовує зміни, якщо вони були розпочаті (хоча тут поки що нічого відкочувати, але це гарний тон).
Ця лекція присвячена реальним сценаріям використання транзакцій, тому я вирішив навести тут приклад із реального життя. Він містить збережену процедуру і написаний з допомогою PL-SQL. Думаю, у тебе вже достатньо досвіду, щоб зрозуміти, як тут все працює. У майбутньому ми повернемося до теми PL-SQL і розглянемо навіть набагато складніші приклади.
Масове оновлення даних у транзакції
Створення транзакції корисне не лише для задач із переказом коштів. Припустимо, у нас є база даних інтернет-магазину, де щодня десятки замовлень можуть змінювати свої статуси, наприклад, з "у доставці" на "завершено". Як оновити багато записів одразу так, щоб у разі збою залишалася можливість відкотити зміни? Звісно, використати транзакцію.
Розглянемо ще один сценарій: оновлення статусів замовлень.
Ось приклад:
-- Починаємо транзакцію
BEGIN;
-- Крок 1. Оновлюємо замовлення з датою доставки, що вже минула
UPDATE orders
SET status = 'completed'
WHERE delivery_date < CURRENT_DATE;
-- Крок 2. Повідомляємо про успішне оновлення
RAISE NOTICE 'Всі статуси замовлень оновлені успішно.';
-- Застосовуємо зміни
COMMIT;
А якщо щось пішло не так?
Завжди є ймовірність помилки. Наприклад, ти випадково забув вказати умову WHERE, і тепер всі замовлення змінили свій статус на completed. Щоб уникнути таких ситуацій, важливо завершити транзакцію або явно її відкотити.
Розглянемо сценарій із відкотом:
-- Починаємо транзакцію
BEGIN;
-- Крок 1. Спроба оновити замовлення без умови (ой, помилка!)
UPDATE orders
SET status = 'completed';
-- Відкат транзакції через помилку
ROLLBACK;
-- Тепер замовлення залишилися незмінними
Додаємо трохи "гнучкості" з SAVEPOINT
Не завжди треба відкотити всю транзакцію. Якщо твій сценарій складається з кількох дій, можливо, тобі треба відкотити лише частину. Тут на допомогу приходить SAVEPOINT.
Тепер наш сценарій такий: обробка кількох кроків із можливістю відкотити один із них.
Уяви, що ти обробляєш замовлення з кількох кроків: списання товарів зі складу, оновлення статусу замовлення, відправка повідомлення клієнту. Якщо повідомлення не відправилося успішно, ти хочеш відкотити лише цей крок, але зберегти зміни в базі.
-- Почати транзакцію
BEGIN;
-- Крок 1. Списуємо товари зі складу
UPDATE products
SET stock = stock - 1
WHERE product_id = 101;
-- Зберігаємо точку відкату
SAVEPOINT step1;
-- Крок 2. Оновлюємо статус замовлення
UPDATE orders
SET status = 'shipped'
WHERE order_id = 202;
-- Спроба відправити повідомлення клієнту
SAVEPOINT step2;
-- Ой, помилка в процесі повідомлення!
ROLLBACK TO SAVEPOINT step2;
-- Вирішуємо, що завершити транзакцію безпечно
COMMIT;
Висновок
Транзакції — це не просто технічний інструмент, це гарантія цілісності твоїх даних. Вони захищають від "ефекту доміно", коли одна помилка може зламати всю систему. Кожного разу, коли ти виконуєш кілька пов’язаних операцій, запитуй себе: "А що, якщо одна з них впаде?" Якщо відповідь звучить як "катастрофа", значить, час юзати транзакцію. Пам’ятай: краще витратити кілька хвилин на написання транзакції, ніж кілька годин на відновлення даних після збою. Твої користувачі (і твої нерви) скажуть тобі дякую!
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ