Пошук уроків, статей та іншого контенту
Розберете атомарність, узгодженість, ізольованість і довговічність на практичних прикладах.
ACID — це чотири властивості транзакцій у базах даних:
Atomicity — атомарність
Consistency — узгодженість
Isolation — ізольованість
Durability — довговічність
Разом вони допомагають виконувати операції надійно навіть у разі помилок, одночасної роботи кількох користувачів або збою системи.
Транзакція — це група операцій, яку база даних розглядає як одну логічну дію.
Наприклад, переказ грошей між двома рахунками складається щонайменше з двох операцій:
Зняти кошти з рахунку відправника.
Додати кошти на рахунок отримувача.
Ці операції повинні виконатися разом. Не можна допустити ситуацію, коли гроші з одного рахунку зняли, але на інший не зарахували.
У PostgreSQL транзакція зазвичай має такий вигляд:
BEGIN;
-- Операції транзакції
COMMIT;BEGIN починає транзакцію.
COMMIT підтверджує всі зміни.
ROLLBACK скасовує всі зміни після BEGIN.
Атомарність означає: транзакція виконується повністю або не виконується зовсім.
Уявімо переказ 100 одиниць між рахунками:
BEGIN;
UPDATE accounts
SET balance = balance - 100
WHERE id = 1;
UPDATE accounts
SET balance = balance + 100
WHERE id = 2;
COMMIT;Якщо друга операція завершиться помилкою, потрібно скасувати і першу:
ROLLBACK;У такому разі рахунок відправника не втратить кошти.
Нижче наведений повний приклад, який можна виконати в PostgreSQL:
DROP TABLE IF EXISTS accounts;
CREATE TABLE accounts (
id integer PRIMARY KEY,
owner_name text NOT NULL,
balance numeric(12, 2) NOT NULL CHECK (balance >= 0)
);
INSERT INTO accounts (id, owner_name, balance)
VALUES
(1, 'Олена', 1000.00),
(2, 'Андрій', 500.00);
BEGIN;
UPDATE accounts
SET balance = balance - 100.00
WHERE id = 1 AND balance >= 100.00;
UPDATE accounts
SET balance = balance + 100.00
WHERE id = 2;
COMMIT;
SELECT id, owner_name, balance
FROM accounts
ORDER BY id;Після виконання:
баланс Олени зменшиться до 900.00;
баланс Андрія збільшиться до 600.00;
загальна сума залишиться 1500.00.
Щоб побачити скасування змін:
BEGIN;
UPDATE accounts
SET balance = balance - 200.00
WHERE id = 1;
ROLLBACK;
SELECT id, owner_name, balance
FROM accounts
ORDER BY id;Після ROLLBACK баланс повернеться до значення, яке було до початку транзакції.
Узгодженість означає, що після завершення транзакції база даних залишається у правильному стані відповідно до встановлених правил.
Ці правила задають обмеження таблиць:
PRIMARY KEY не дозволяє дублювати ідентифікатори;
NOT NULL не дозволяє пропускати обов’язкові значення;
CHECK перевіряє умову;
FOREIGN KEY гарантує зв’язок із наявним записом;
UNIQUE не дозволяє дублювати унікальні значення.
У прикладі з рахунками є обмеження:
balance numeric(12, 2) NOT NULL CHECK (balance >= 0)Воно не дозволяє зберегти від’ємний баланс:
INSERT INTO accounts (id, owner_name, balance)
VALUES (3, 'Марія', -50.00);PostgreSQL відхилить таку операцію, тому що вона порушує умову balance >= 0.
Узгодженість також залежить від логіки транзакції. Наприклад, переказ має змінити обидва рахунки, а не лише один. Обмеження таблиці захищають дані від окремих некоректних значень, а транзакція об’єднує пов’язані зміни.
Ізольованість означає, що паралельні транзакції не повинні некоректно впливати одна на одну.
Уявімо, що двоє користувачів одночасно працюють з одним рахунком. Один користувач змінює баланс, а інший у цей момент читає дані.
PostgreSQL використовує механізм багатоверсійності даних. Це дає змогу транзакціям працювати з узгодженим представленням даних і не бачити незавершені зміни інших транзакцій.
Відкрийте два підключення до PostgreSQL: сесію A і сесію B.
Спочатку в сесії A виконайте:
BEGIN;
UPDATE accounts
SET balance = balance - 50
WHERE id = 1;Зміна поки що не підтверджена через COMMIT.
У сесії B виконайте:
SELECT balance
FROM accounts
WHERE id = 1;Сесія B побачить попереднє підтверджене значення балансу, а не незавершену зміну сесії A.
Тепер у сесії A підтвердьте транзакцію:
COMMIT;Після цього повторіть запит у сесії B:
SELECT balance
FROM accounts
WHERE id = 1;Новий запит побачить уже змінене значення.
За замовчуванням PostgreSQL використовує рівень ізоляції READ COMMITTED. Кожна окрема SQL-команда бачить дані, підтверджені до початку цієї команди.
Рівень ізоляції можна вказати явно:
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT balance
FROM accounts
WHERE id = 1;
COMMIT;У цьому режимі всі запити однієї транзакції працюють з узгодженим знімком даних.
Для особливо чутливих операцій можна використати SERIALIZABLE:
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SELECT balance
FROM accounts
WHERE id = 1;
UPDATE accounts
SET balance = balance - 10
WHERE id = 1;
COMMIT;Якщо PostgreSQL виявить конфлікт між паралельними транзакціями, одна з них може завершитися помилкою серіалізації. Застосунок має повторити таку транзакцію.
Довговічність означає, що після успішного COMMIT зміни не повинні зникнути через звичайний збій процесу або перезапуск сервера бази даних.
Наприклад:
BEGIN;
UPDATE accounts
SET balance = balance + 250.00
WHERE id = 2;
COMMIT;Після успішного COMMIT PostgreSQL вважає зміну збереженою. Якщо з’єднання із сервером одразу після цього перерветься, підтверджена зміна не повинна бути скасована.
Для забезпечення довговічності PostgreSQL використовує журнал передзапису — WAL, тобто Write-Ahead Log. Перед збереженням змін база даних записує інформацію про них у журнал.
Важливо відрізняти:
транзакція завершилася через COMMIT — зміни підтверджені;
транзакція ще виконується — зміни можуть бути скасовані;
виконано ROLLBACK — зміни відкинуто.
Переказ між рахунками демонструє всі властивості ACID:
Атомарність
Зняття і зарахування коштів відбуваються разом.
Узгодженість
Баланс не може стати від’ємним, а записи мають відповідати обмеженням таблиці.
Ізольованість
Інші транзакції не бачать незавершений переказ як готовий результат.
Довговічність
Після COMMIT підтверджений переказ зберігається.
BEGIN;
-- Перевірка та зміна рахунку відправника
UPDATE accounts
SET balance = balance - 75.00
WHERE id = 1 AND balance >= 75.00;
-- Зміна рахунку отримувача
UPDATE accounts
SET balance = balance + 75.00
WHERE id = 2;
COMMIT;Якщо під час виконання виникла помилка, транзакцію потрібно скасувати:
ROLLBACK;У прикладному коді важливо обробляти помилки бази даних і не залишати транзакцію незавершеною.
Небезпечно виконувати переказ двома незалежними запитами:
UPDATE accounts
SET balance = balance - 100
WHERE id = 1;
UPDATE accounts
SET balance = balance + 100
WHERE id = 2;Якщо між цими запитами станеться помилка, виконається лише перша частина операції.
COMMIT занадто раноЯкщо підтвердити транзакцію після першої операції, скасувати весь переказ уже не вийде:
BEGIN;
UPDATE accounts
SET balance = balance - 100
WHERE id = 1;
COMMIT;
UPDATE accounts
SET balance = balance + 100
WHERE id = 2;Обидві зміни повинні бути всередині однієї транзакції.
Якщо один із запитів завершився помилкою, не слід продовжувати логічну операцію так, ніби все успішно. Потрібно обробити помилку й виконати ROLLBACK.
COMMIT перевіряє всю бізнес-логікуCOMMIT підтверджує виконані операції та перевіряє обмеження бази даних. Але він не знає всіх правил предметної області. Наприклад, база даних може не знати, що переказ має бути заборонений після завершення робочого дня. Такі правила потрібно реалізовувати в логіці застосунку або в додаткових обмеженнях бази даних.
Атомарність: транзакція виконується повністю або повністю скасовується.
Узгодженість: дані після транзакції відповідають правилам та обмеженням.
Ізольованість: паралельні транзакції не бачать некоректні проміжні результати одна одної.
Довговічність: після COMMIT підтверджені зміни зберігаються.
Для початку транзакції використовують BEGIN.
Для підтвердження змін використовують COMMIT.
Для скасування змін використовують ROLLBACK.
Пов’язані операції, наприклад переказ коштів, потрібно виконувати в межах однієї транзакції.