Пошук уроків, статей та іншого контенту
Як гарантувати, що кілька пов'язаних змін застосовуються всі разом або жодна — BEGIN, COMMIT, ROLLBACK.
Переказ грошей між двома рахунками — класичний приклад операції, що складається з кількох окремих SQL-команд (зменшити баланс одного рахунку, збільшити баланс іншого), які логічно мають виконатись разом, як одна неподільна дія. Якщо сервер аварійно завершиться між цими двома командами, гроші «зникнуть» — списані з одного рахунку, але ще не зараховані на інший.
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT; -- обидві зміни застосовуються атомарно, разомBEGIN відкриває транзакцію — усі наступні команди виконуються як єдине ціле. COMMIT підтверджує й застосовує всі зміни транзакції одночасно. Якщо щось пішло не так (помилка, свідоме рішення скасувати), ROLLBACK відкочує геть усі зміни транзакції, ніби їх ніколи й не було:
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- Перевірка: чи не пішов баланс у мінус?
SELECT balance FROM accounts WHERE id = 1;
-- Якщо баланс від'ємний — скасувати всю операцію:
ROLLBACK;Ключова гарантія — інші запити до бази даних під час незавершеної транзакції не бачать проміжного, ще не підтвердженого стану (залежно від рівня ізоляції — детально нижче). Для зовнішнього спостерігача транзакція виглядає як миттєва, неподільна дія: або весь набір змін уже застосовано, або жодної з них.
PostgreSQL підтримує кілька рівнів ізоляції транзакцій, що визначають, наскільки паралельні транзакції можуть впливати одна на одну — від READ COMMITTED (типовий за замовчуванням, кожен запит у межах транзакції бачить дані, підтверджені на момент його виконання) до SERIALIZABLE (найсуворіший, гарантує поведінку, ідентичну послідовному виконанню транзакцій одна за одною, ціною продуктивності при високому паралелізмі).
Для більшості застосунків типовий рівень ізоляції (READ COMMITTED) достатній. SERIALIZABLE вартий розгляду для операцій, де навіть рідкісна помилка узгодженості при високому паралелізмі неприйнятна (фінансові операції, системи бронювання з обмеженою кількістю місць) — і варта усвідомленого обміну на нижчу пропускну здатність під навантаженням.
Без транзакції й належного рівня ізоляції два одночасних запити «перевір залишок товару, потім зменш його на 1» можуть обидва прочитати «залишок 1», обидва вирішити, що продаж можливий, і обидва зменшити залишок — товар проданий двічі, хоча в наявності був лише один. Транзакція з відповідним рівнем ізоляції (чи атомарна SQL-команда на кшталт UPDATE ... WHERE stock > 0) запобігає цьому.
Виконувати кілька логічно пов'язаних UPDATE/INSERT без обгортання в транзакцію — залишає можливість часткового застосування змін при збої посередині.
Тримати транзакцію відкритою занадто довго (наприклад, чекаючи відповіді зовнішнього API всередині BEGIN/COMMIT) — блокує пов'язані рядки для інших транзакцій на весь цей час, знижуючи паралелізм системи.
Забути ROLLBACK при обробці помилки всередині транзакції — залишена відкритою транзакція продовжує утримувати блокування ресурсів, поки з'єднання не завершиться.
Транзакція (BEGIN/COMMIT/ROLLBACK) гарантує, що набір пов'язаних змін застосовується атомарно — усі разом або жодна, навіть при збої посередині. Рівень ізоляції визначає, наскільки паралельні транзакції можуть впливати одна на одну — типовий READ COMMITTED достатній для більшості випадків, SERIALIZABLE — для операцій, де навіть рідкісна неузгодженість під високим паралелізмом неприйнятна.