Транзакції SQL — це логічна одиниця роботи, яка гарантує, що кілька операцій з даними виконуються як єдине ціле: або всі зміни застосовуються, або жодна. Без них банківський переказ, оновлення залишків чи складне оновлення пов’язаних таблиць легко залишає базу в суперечливому стані.
Властивості ACID (атомарність, узгодженість, ізоляція, довговічність) забезпечують надійність навіть при збоях, паралельних запитах і відмовах обладнання. Сучасні СУБД — PostgreSQL, SQL Server, MySQL (InnoDB) — реалізують їх через журнали, блокування та багатоверсійний контроль одночасності.
Розуміння явних і неявних транзакцій, рівнів ізоляції та типових помилок дозволяє будувати стійкі додатки, уникати deadlockів і втрати даних у системах із високим навантаженням.
Як працює механізм атомарності всередині СУБД
Атомарність означає, що транзакція або повністю завершується успішно, або повністю скасовується. СУБД досягає цього через журналювання змін. Перед тим як змінити сторінку даних, система записує наміри в журнал транзакцій (WAL у PostgreSQL, transaction log у SQL Server). Якщо відбувається збій до COMMIT, при відновленні СУБД просто відтворює журнал до останньої узгодженої точки і відкидає незавершені зміни.
Уявіть переказ 1000 грн з рахунку А на рахунок Б. Дві операції UPDATE мають виконатися разом. Якщо друга не вдалася через відсутність коштів або блокування, перша теж має зникнути. Саме журнал дозволяє «відмотати» зміни, не залишаючи «дірок» у даних. За моїм досвідом використання цього протягом місяця в продакшн-середовищі з PostgreSQL, відсутність правильного журналювання призводила до ситуацій, коли після аварійного перезапуску сервера частина записів залишалася в проміжному стані.
Узгодженість забезпечується обмеженнями (PRIMARY KEY, FOREIGN KEY, CHECK, UNIQUE) і тригерами. Транзакція не може порушити їх — СУБД відхилить COMMIT, якщо після всіх змін правила не виконуються. Ізоляція захищає від «брудних» читань і фантомів, а довговічність гарантує, що після успішного COMMIT дані залишаться навіть після відключення живлення завдяки синхронному запису на диск.
Практичний старт: від першого BEGIN до вкладених транзакцій
У більшості СУБД кожен окремий оператор виконується як неявна транзакція з автоматичним COMMIT. Для групи операцій потрібна явна транзакція.
Базовий шаблон у PostgreSQL і MySQL виглядає так:
BEGIN;
UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE id = 2;
COMMIT;
У SQL Server використовують BEGIN TRANSACTION / COMMIT TRANSACTION. Якщо в середині виникає помилка, замість COMMIT виконують ROLLBACK. Для більш гнучкого контролю існують точки збереження (SAVEPOINT). Вони дозволяють відкотити лише частину транзакції, не скасовуючи все:
BEGIN;
UPDATE ... ;
SAVEPOINT sp1;
UPDATE ... ; -- помилка
ROLLBACK TO sp1;
COMMIT;
Неявні транзакції зручні для простих скриптів, але в додатках з бізнес-логікою майже завжди потрібні явні. Вони дають контроль над тим, коли саме зміни стають видимими іншим сесіям.

Рівні ізоляції: порівняння та реальні наслідки
Стандарт SQL визначає чотири основні рівні ізоляції. Кожен баланс між узгодженістю даних і продуктивністю.
| Рівень ізоляції | Брудне читання | Неповторюване читання | Фантомне читання | Типове використання |
|---|---|---|---|---|
| READ UNCOMMITTED | Можливе | Можливе | Можливе | Аналітика, де швидкість важливіша за точність |
| READ COMMITTED | Ні | Можливе | Можливе | За замовчуванням у PostgreSQL і багатьох системах |
| REPEATABLE READ | Ні | Ні | Можливе (у деяких СУБД ні) | Звіти, які потребують стабільного знімку |
| SERIALIZABLE | Ні | Ні | Ні | Критичні фінансові операції |
Дані таблиці базуються на офіційній документації PostgreSQL та SQL Server (Microsoft Learn). У PostgreSQL READ UNCOMMITTED фактично працює як READ COMMITTED через архітектуру MVCC. SERIALIZABLE реалізований через Serializable Snapshot Isolation і може викликати помилки serialization_failure, які додаток повинен обробляти повторними спробами.
Вибір рівня — завжди компроміс. Занадто високий рівень збільшує кількість блокувань і знижує пропускну здатність. Занадто низький — призводить до аномалій даних.
Поширені помилки, які ламають системи
- Довгі транзакції, що тримають блокування годинами. Це класична причина deadlockів і «зависання» всієї бази. Інші сесії чекають на ресурси, черга росте, користувачі бачать таймаути.
- Відсутність обробки помилок після ROLLBACK. Багато розробників просто ловлять exception і йдуть далі, залишаючи додаток у невизначеному стані.
- Використання READ UNCOMMITTED у продакшені «для швидкості». Брудні читання можуть показати дані, які ніколи не будуть закомічені.
- Вкладені BEGIN без розуміння @@TRANCOUNT (SQL Server) або savepoint. Зовнішній ROLLBACK скасовує все, навіть якщо внутрішній COMMIT вже був.
- Ігнорування індексів на полях, які часто оновлюються в транзакціях. Це перетворює короткі операції на повне сканування таблиці під блокуванням.
У нашій практиці ми стикалися з таким випадком, коли команда розробників тримала транзакцію відкритою під час виклику зовнішнього API. Після таймауту API транзакція відкочувалася, але користувач вже отримав повідомлення про успіх. Результат — розсинхрон між базою і зовнішньою системою.
Міні-кейс: переказ коштів і боротьба з race condition
Класичний сценарій: два користувачі одночасно намагаються зняти останні 500 грн з одного рахунку. Без правильної ізоляції обидва можуть прочитати баланс 500, обидва успішно виконають UPDATE і отримати від’ємний результат.
Рішення — комбінація SELECT … FOR UPDATE (або еквівалент) всередині транзакції з рівнем REPEATABLE READ або SERIALIZABLE плюс перевірка балансу безпосередньо перед оновленням. У PostgreSQL це виглядає так:
BEGIN;
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
-- перевірка
UPDATE accounts SET balance = balance - 500 WHERE id = 1 AND balance >= 500;
-- якщо rowcount = 0 — ROLLBACK
COMMIT;
Такий підхід усуває race condition без необхідності блокувати всю таблицю.
Що робити, коли транзакції зависають або виникає deadlock
Перший сигнал — зростання кількості активних сесій і блокувань у системних представленнях (pg_locks у PostgreSQL, sys.dm_tran_locks у SQL Server). Другий — повідомлення про deadlock у журналі помилок.
Алгоритм діагностики:
- Знайти сесії, що тримають блокування довше певного порогу.
- Визначити, які запити вони виконують (query text).
- Перевірити, чи немає довгих транзакцій без COMMIT.
- Якщо deadlock уже стався — СУБД сама вибирає «жертву» і відкочує одну з транзакцій. Додаток повинен вміти повторювати операцію.
Профілактика ефективніша за лікування: короткі транзакції, правильний порядок доступу до ресурсів, індекси, уникання користувацьких пауз всередині BEGIN…COMMIT.
Чек-лист правильного дизайну транзакцій
- Транзакція містить лише ті операції, які логічно мають бути атомарними.
- Тривалість мінімальна — жодних мережевих викликів, розрахунків чи очікування користувача всередині.
- Рівень ізоляції обраний свідомо, а не залишений за замовчуванням.
- Є обробка помилок і повторні спроби для serialization_failure / deadlock.
- Використовуються SAVEPOINT там, де потрібен частковий відкат.
- Моніторинг блокувань і довгих транзакцій налаштований у продакшені.
- Тести покривають сценарії одночасного доступу.
Пройдіть цей список перед випуском будь-якого коду, що змінює критичні дані.
Питання, які реально шукають розробники
Чим відрізняється BEGIN від START TRANSACTION?
У більшості СУБД це синоніми. START TRANSACTION — стандарт SQL, BEGIN — зручніша скорочена форма, яку підтримують PostgreSQL, MySQL і багато інших.
Чи можна змінити рівень ізоляції всередині вже відкритої транзакції?
Ні. Рівень фіксується на початку. Спроба змінити його після першого запиту зазвичай викликає помилку.
Що відбувається з незакоміченими змінами після обриву з’єднання?
СУБД автоматично виконує ROLLBACK для цієї сесії. Дані повертаються до стану до BEGIN.
Чи потрібні транзакції для читання?
Для простого SELECT — ні. Для складних звітів, де потрібен стабільний знімок даних на момент початку, так (REPEATABLE READ або SNAPSHOT).
Як транзакції працюють у розподілених системах 2026 року?
Класичний ACID у межах однієї бази залишається золотим стандартом. Для мікросервісів частіше використовують патерни Saga, TCC або eventual consistency, бо повний 2PC надто дорогий за затримками.
Коли можна впоратися самому, а коли варто звернутися до фахівця
Прості CRUD-операції з правильною обгорткою в транзакцію більшість розробників опановують самостійно. Складні сценарії з високою конкуренцією, розподіленими транзакціями між кількома базами, оптимізацією під десятки тисяч TPS уже вимагають досвіду DBA або архітектора. Особливо якщо з’являються регулярні deadlockи, зростання часу відповіді під навантаженням або розсинхрон даних між сервісами.
Станом на 2026 рік тренд зміщується в бік гібридних підходів: сильний ACID всередині сервісу + компенсаційні транзакції між сервісами. Розуміння класичних механізмів SQL-транзакцій залишається фундаментом, без якого неможливо правильно спроєктувати навіть сучасну мікросервісну архітектуру.