Транзакции и свойства ACID
Перевод денег, оформление заказа, бронирование — операции, которые должны выполниться «всё или ничего». За это отвечают транзакции и свойства ACID.
Транзакция — это логически неделимая последовательность операций над базой данных, которая переводит её из одного согласованного состояния в другое. Либо все операции транзакции применяются, либо ни одна.
Зачем нужны транзакции
Классический пример — перевод денег: списать 1000 рублей с одного счёта и зачислить на другой. Это две операции, но для бизнеса они — одно действие. Что если после списания, но до зачисления случится сбой? Деньги исчезнут. Транзакция гарантирует, что такого «полусостояния» не будет: либо обе операции выполнятся, либо ни одна. То же в магазине: оформление заказа — это и запись заказа, и списание товара со склада, и резерв оплаты. Все эти шаги должны быть единым целым.
Транзакция ограничивается командами: начало (BEGIN), успешное завершение (COMMIT — зафиксировать изменения) и откат (ROLLBACK — отменить всё, что сделано с начала транзакции). До COMMIT изменения как бы «черновик», который можно отменить целиком.
Аналогия: бронирование билета
Чтобы свойства транзакции улеглись интуитивно, представьте покупку билета в кино онлайн. Вы выбираете место, вводите карту, подтверждаете оплату. С точки зрения системы это цепочка шагов: пометить место занятым, списать деньги, выдать билет. Атомарность требует: либо вы получаете билет и деньги списаны, либо место свободно и деньги на месте — но никогда «деньги списали, а билета нет» или «билет выдали, а место отдали и другому». Согласованность: одно и то же место не может оказаться продано дважды (инвариант). Изоляция: пока вы оформляете место №5, другой покупатель, кликнувший на №5 одновременно, не должен суметь купить его же — система обязана развести вас. Долговечность: купленный билет не должен исчезнуть, если сервер перезагрузится через секунду после оплаты. Эта бытовая картинка покрывает все четыре свойства разом и хорошо показывает, что ACID — не абстракция, а ровно то, чего мы интуитивно ждём от честной системы.
Живой пример: атомарный перевод
Запустите этот SQL: мы оборачиваем два UPDATE в транзакцию. Если что-то пойдёт не так между ними, можно откатить всё.
CREATE TABLE accounts (id INTEGER PRIMARY KEY, owner TEXT, balance INTEGER);
INSERT INTO accounts VALUES (1,'Анна',1000),(2,'Борис',500);
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 300 WHERE id = 1; -- списали у Анны
UPDATE accounts SET balance = balance + 300 WHERE id = 2; -- зачислили Борису
COMMIT;
SELECT owner, balance FROM accounts ORDER BY id;
Вывод: «Анна — 700», «Борис — 800». Сумма денег в системе сохранилась (1500), потому что оба обновления применились вместе по COMMIT. Если бы между ними произошёл сбой, ROLLBACK вернул бы исходные 1000 и 500 — и ни рубля бы не потерялось.
Жизненный цикл транзакции
Полезно представлять транзакцию как объект с состояниями. Она рождается активной (active) — выполняет операции чтения и записи. Закончив работу, переходит в состояние «частично зафиксирована» (partially committed) — все операции выполнены, но изменения ещё не гарантированно на диске. Если всё прошло успешно и журнал сброшен на устойчивый носитель, транзакция становится committed — зафиксированной навсегда. Если же на любом этапе случилась ошибка (нарушение ограничения, сбой, явный ROLLBACK), транзакция переходит в failed, а затем aborted — все её изменения откатываются, будто её и не было. Из aborted транзакцию обычно можно перезапустить. Эта модель состояний — не формальность: именно она объясняет, почему «частично выполненной» транзакции для внешнего мира не существует. Наблюдатель видит лишь два исхода — committed или aborted, — и никогда промежуточное состояние. В этом и состоит атомарность с точки зрения жизненного цикла.
Свойства ACID
Гарантии транзакций описывают четырьмя свойствами — аббревиатура ACID. Это контракт, который СУБД даёт приложению.
Atomicity (атомарность)
Транзакция неделима: выполняются либо все её операции, либо ни одна. Промежуточных состояний для внешнего мира не существует. Если на середине случается сбой или вызывается ROLLBACK, все уже сделанные изменения откатываются. Это та самая гарантия «всё или ничего» из примера с переводом.
Consistency (согласованность)
Транзакция переводит базу из одного согласованного состояния в другое, не нарушая ограничений целостности (первичные и внешние ключи, CHECK, бизнес-инварианты). Если транзакция попыталась бы оставить базу в недопустимом состоянии (например, нарушить ссылочную целостность), она будет отклонена. Внутри транзакции инвариант может временно нарушаться, но на границах (до и после) — обязан выполняться.
Isolation (изолированность)
Параллельно выполняемые транзакции не должны мешать друг другу: каждая работает так, будто она в системе одна. Результат одновременного выполнения должен совпадать с результатом какого-то последовательного. На практике полная изоляция дорога, поэтому СУБД предлагают уровни изоляции — это тема следующего урока.
Durability (долговечность)
После успешного COMMIT изменения сохранены навсегда — даже если сразу после этого отключат питание. Достигается записью в журнал на устойчивый носитель до подтверждения коммита. Подробнее о журналировании — в третьем уроке раздела.
У долговечности есть тонкость, которую часто упускают. Гарантия «сохранено навсегда» наступает строго в момент успешного возврата из COMMIT, а не раньше. Пока COMMIT не вернул управление, ваши изменения — лишь кандидаты, которые сбой может стереть. Поэтому корректный код считает операцию завершённой только после подтверждения коммита, а не после последнего UPDATE. Эта дисциплина особенно важна в денежных и юридически значимых операциях: «я отправил запрос» и «база гарантировала сохранность» — разные моменты, и между ними возможен сбой.
| Свойство | Гарантия | Защищает от |
| Atomicity | Всё или ничего | частичного применения при сбое |
| Consistency | Соблюдение ограничений | недопустимых состояний базы |
| Isolation | Невмешательство параллельных транзакций | гонок и грязных данных |
| Durability | Сохранность после COMMIT | потери данных при сбое |
Где границы транзакции: искусство, а не только техника
Решить, что именно обернуть в одну транзакцию, — содержательное проектное решение, и ошибки тут дорого стоят. Слишком узкие границы разрывают атомарность: если списание и зачисление в переводе попадут в разные транзакции, сбой между ними потеряет деньги — ровно то, от чего мы защищались. Слишком широкие границы вредят иначе: транзакция, охватывающая много операций и долго не фиксирующаяся, удерживает блокировки и ресурсы, мешая другим (об этом — в уроке про параллелизм), и повышает риск отката большого объёма работы. Хорошее правило: границы транзакции должны совпадать с границами бизнес-операции — того, что для пользователя является одним неделимым действием. «Оформить заказ» — одна транзакция, даже если внутри пять шагов. «Загрузить отчёт за месяц по строчке» — точно не одна гигантская транзакция. Чувство этих границ приходит с опытом, но осознанный вопрос «что здесь неделимо для бизнеса?» направляет верно.
Согласованность как совместная ответственность
Тонкий момент: из четырёх свойств три (атомарность, изоляция, долговечность) обеспечивает сама СУБД, а согласованность — совместная ответственность. СУБД следит за объявленными ограничениями (ключи, CHECK), но смысловые инварианты (например, «сумма денег при переводе сохраняется») должен заложить разработчик в логику транзакции. СУБД не знает, что баланс нельзя «терять», — она лишь гарантирует, что ваша корректная транзакция выполнится атомарно.
Типичные ошибки
- Не оборачивают связанные операции в транзакцию. Списание и зачисление выполнили двумя отдельными командами — при сбое между ними деньги теряются.
- Путают согласованность и изоляцию. Согласованность — про соблюдение ограничений; изоляция — про невмешательство параллельных транзакций.
- Полагаются на «согласованность» как на магию. Бизнес-инвариант обязан заложить разработчик; СУБД проверяет только объявленные ограничения.
- Считают данные сохранёнными до COMMIT. До коммита изменения можно потерять при сбое; долговечность наступает только после успешного
COMMIT.
Итог
- Транзакция — неделимая последовательность операций; границы задают
BEGIN,COMMIT,ROLLBACK. - ACID: атомарность (всё или ничего), согласованность (ограничения целостности), изоляция (невмешательство), долговечность (сохранность после COMMIT).
- Атомарность, изоляцию и долговечность обеспечивает СУБД; смысловую согласованность закладывает разработчик.
- Без транзакций параллельные и сбойные сценарии ведут к потере и порче данных.