JavaRush /Курси /SQL SELF /Робота з транзакціями для забезпечення цілісності даних

Робота з транзакціями для забезпечення цілісності даних

SQL SELF
Рівень 22 , Лекція 2
Відкрита

Давай уявимо, що ти пишеш додаток для інтернет-магазину, і під час оплати замовлення тобі потрібно:

  1. Утримати гроші з картки клієнта.
  2. Зменшити кількість товару на складі.
  3. Створити запис про успішну транзакцію.

Що буде, якщо посеред цих дій щось піде не так? Наприклад, товар на складі закінчився після того, як гроші вже утримали, але до створення запису про замовлення? Все піде шкереберть: гроші "зависли", замовлення не завершене, а твій сервер отримує купу злих листів (і, можливо, судових позовів).

Транзакції якраз допомагають уникнути таких ситуацій. Вони дозволяють згрупувати кілька операцій в одну "атомарну" одиницю роботи з базою даних. Це як кнопка "Відміна" у текстовому редакторі: якщо щось пішло не так, просто відкочуєшся на початок.

Як транзакції забезпечують цілісність даних?

Транзакції базуються на концепції ACID:

  • Атомарність (Atomicity) — Всі операції всередині транзакції виконуються або повністю, або нічого не виконується. "Все або нічого".
  • Узгодженість (Consistency) — Дані залишаються в узгодженому стані до і після виконання транзакції.
  • Ізоляція (Isolation) — Одна транзакція не заважає іншим.
  • Довговічність (Durability) — Коли транзакція завершена, її результат зберігається навіть у разі збою системи.

Чому я це знову повторюю? Бо це той ідеал, до якого всі прагнуть. І... який рідко досяжний. Коли ми вдруге повернемося до транзакцій у нашому курсі, ти зрозумієш, що деякими ACID-принципами доведеться пожертвувати.

Тож насолоджуйся часом, коли транзакції такі прості й красиві. І давай вже перейдемо до прикладів!

Приклад використання транзакцій

Давай розглянемо сценарій додавання студента і реєстрації його на курс.

Припустимо, ми працюємо з базою даних університету. У нас з'явилися позаштатні слухачі наших курсів. Якщо на курсі є місце, тоді такого слухача реєструємо як студента (тимчасово) і додаємо на курс. Ось як це буде відбуватися.

При додаванні нового студента в базу і його реєстрації на курс нам потрібно:

  1. Додати запис у таблицю students.
  2. Створити запис у таблиці enrollments, що зв'язує студента з курсом.

Якщо щось піде не так (наприклад, курс вже заповнений), ми маємо відкотити операцію, щоб дані не розійшлися між таблицями. Ось як це робиться:

-- Початок транзакції
BEGIN;

-- Крок 1: Додаємо студента
INSERT INTO students (name, age, gender)
VALUES ('Otto Lin', 20, 'Male')
RETURNING id;

-- Припустимо, повернувся id = 10

-- Крок 2: Реєструємо його на курс
INSERT INTO enrollments (student_id, course_id)
VALUES (10, 5);

-- Все пройшло успішно? Фіксуємо зміни
COMMIT;

Що відбувається, якщо виникає помилка?

Раптом сталася помилка при реєстрації на курс: наприклад, курс не існує. Якщо ти забудеш про транзакцію, то запис про студента в таблиці students залишиться, а в таблиці enrollments — ні. Це порушує цілісність даних. Щоб цього уникнути, можна використати команду ROLLBACK.

-- Початок транзакції
BEGIN;

-- Крок 1: Додаємо студента
INSERT INTO students (name, age, gender)
VALUES ('Otto Lin', 20, 'Male')
RETURNING id;

-- Крок 2: Пробуємо зареєструвати його на курс
INSERT INTO enrollments (student_id, course_id)
VALUES (10, 999); -- Помилка: курсу з id = 999 не існує!

-- Відкатуємо всі зміни
ROLLBACK;

У результаті жодна з операцій не виконається, і база даних залишиться в тому ж стані, що й до транзакції.

Використання SAVEPOINT для контролю

Тепер уяви складніший сценарій. Ти хочеш виконати кілька операцій, але на якомусь етапі потрібно відкотитися тільки до певної точки, а не скасовувати весь процес.

Давай реалізуємо покрокову реєстрацію студента

-- Початок транзакції
BEGIN;

-- Додаємо студента
SAVEPOINT add_student; -- Створюємо точку збереження
INSERT INTO students (name, age, gender)
VALUES ('Anna Song', 22, 'Female');

-- Реєструємо її на перший курс
SAVEPOINT enroll_course_1; -- Ще одна точка збереження
INSERT INTO enrollments (student_id, course_id)
VALUES (11, 5);

-- Реєструємо її на другий курс (тут помилка)
INSERT INTO enrollments (student_id, course_id)
VALUES (11, 999); -- Помилка!

-- Відкатуємося тільки до останньої точки збереження
ROLLBACK TO enroll_course_1;

-- Відновлюємо процес
INSERT INTO enrollments (student_id, course_id)
VALUES (11, 6);

-- Фіксуємо зміни
COMMIT;

Таким чином, помилки в одній частині процесу не заважають збереженню даних в інших.

Перевірка на наявність змін

Якщо SQL-запит щось змінює, то є можливість перевірити, чи були реально якісь зміни чи ні.

Може ж бути ситуація, коли ми виконували DELETE, але жоден рядок не потрапив під WHERE. Або виконували UPDATE, а дані вже були змінені і по факту нічого не змінилося.

На цей випадок є спеціальна системна змінна FOUND. Вона показує, чи були зачеплені рядки в останньому SQL-запиті:

  • FOUND = TRUE — запит щось оновив/видалив;
  • FOUND = FALSE — нічого не видалено або не змінено.

З простим SELECT вона не працює, тільки для відстеження змін.

Практичне застосування: обробка платежів

Транзакції особливо корисні у фінансових додатках. Давай знову візьмемося за систему, яка має перевести гроші з одного рахунку на інший.

-- Початок транзакції
BEGIN;

-- Крок 1: Знімаємо гроші з першого рахунку
UPDATE accounts
SET balance = balance - 100
WHERE id = 1 AND balance >= 100;

-- Крок 2: Перевіряємо, що операція успішна (рядки були змінені)
IF NOT FOUND THEN
    ROLLBACK; -- Відкат, якщо коштів недостатньо
    RAISE EXCEPTION 'Недостатньо коштів!'; -- Помилка! Кидаємо виключення
END IF;

-- Крок 3: Додаємо гроші на другий рахунок
UPDATE accounts
SET balance = balance + 100
WHERE id = 2;

-- Фіксуємо транзакцію
COMMIT;

Тут, якщо клієнт намагається перевести більше грошей, ніж є на його рахунку, транзакція відкотиться, і база даних не опиниться у "підвішеному" стані.

Особливості та типові помилки

Забутий COMMIT: якщо наприкінці транзакції забути виконати COMMIT, база даних буде "жити в очікуванні", а зміни не збережуться.

Забутий WHERE: оновлення або видалення даних без умови може призвести до катастрофічних наслідків. Наприклад, команда DELETE FROM students без WHERE видалить усіх студентів.

Довгі транзакції: якщо транзакція відкрита занадто довго, вона може блокувати доступ до даних, що призведе до проблем із продуктивністю. Завжди завершуйте транзакції (COMMIT або ROLLBACK) якнайшвидше.

Транзакції — це твій єдиний друг, коли справа доходить до забезпечення цілісності даних. Вони допомагають уникнути неузгодженості, особливо у складних сценаріях, таких як реєстрація користувачів, обробка платежів або оновлення пов'язаних таблиць. Освоївши роботу з командами BEGIN, COMMIT, ROLLBACK і SAVEPOINT, ти зможеш створювати більш надійні та безпечні додатки.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ