EssayAI
Блог
Блог
Математика и алгоритмы

Транзакции ACID: атомарность, изоляция и надёжность

17 июня 2026Время чтения: 8 минут
#базы данных#транзакции#SQL#ACID#изоляция
Транзакции ACID: атомарность, изоляция и надёжность

Транзакция в базе данных - это последовательность операций, которая выполняется как единое целое. Либо все операции успешно завершаются и фиксируются командой COMMIT, либо при любой ошибке весь блок отменяется командой ROLLBACK. Чтобы гарантировать корректность данных в многопользовательских системах, транзакции должны удовлетворять четырём свойствам, объединённым аббревиатурой ACID. Проверьте свои знания с помощью калькулятора ниже.

Atomicity - атомарность транзакции

Атомарность означает, что транзакция неделима: либо выполняются все её операции, либо ни одна. Принцип «всё или ничего» защищает от ситуаций, когда деньги списаны со счёта отправителя, но не поступили на счёт получателя.

BEGIN;
UPDATE accounts SET balance = balance - 1000 WHERE id = 1;
UPDATE accounts SET balance = balance + 1000 WHERE id = 2;
COMMIT;

Если в момент выполнения второго UPDATE произойдёт сбой, СУБД откатит оба изменения. База данных вернётся в то состояние, которое было до начала транзакции. Механизм реализуется через журнал транзакций (WAL - Write-Ahead Log): все изменения записываются сначала в журнал, и только потом применяются к данным.

Поток транзакции через проверки ACID - при ошибке на любом этапе весь блок откатывается

Consistency - согласованность данных

Согласованность гарантирует, что транзакция переводит базу данных из одного корректного состояния в другое. Все ограничения целостности, правила и триггеры должны соблюдаться как до, так и после транзакции.

Примеры ограничений, которые обеспечивают согласованность:

  • NOT NULL - поле не может быть пустым
  • UNIQUE - значение должно быть уникальным в таблице
  • FOREIGN KEY - ссылочная целостность между таблицами
  • CHECK - пользовательское условие для значений
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    amount DECIMAL CHECK (amount > 0),
    user_id INT REFERENCES users(id)
);

Если транзакция попытается вставить заказ с отрицательной суммой или несуществующим пользователем, СУБД откажет и выполнит ROLLBACK. Ответственность за логическую согласованность лежит на разработчике - СУБД проверяет только декларативные ограничения.

Isolation - изолированность параллельных транзакций

Изолированность - самое сложное из четырёх свойств. Она определяет, насколько одна транзакция видит изменения, вносимые другими параллельными транзакциями. Без изоляции возникают три класса аномалий:

Грязное чтение (Dirty Read) - транзакция читает данные, которые изменила другая незавершённая транзакция. Если та транзакция откатится, прочитанные данные окажутся недействительными.

Неповторяемое чтение (Non-repeatable Read) - транзакция дважды читает одну строку и получает разные значения, потому что другая транзакция успела её изменить и зафиксировать.

Фантомное чтение (Phantom Read) - транзакция дважды выполняет одинаковый запрос с условием и получает разные наборы строк из-за того, что другая транзакция вставила или удалила строки.

Уровни изоляции SQL и аномалии, которые они допускают
Уровни изоляции SQL и аномалии, которые они допускают

Стандарт SQL определяет четыре уровня изоляции, каждый из которых допускает определённые аномалии в обмен на бо́льший параллелизм:

УровеньГрязное чтениеНеповтор.Фантомы
READ UNCOMMITTEDвозможновозможновозможно
READ COMMITTEDнетвозможновозможно
REPEATABLE READнетнетвозможно
SERIALIZABLEнетнетнет

Установить уровень изоляции в PostgreSQL:

BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT * FROM products WHERE category = 'books';
-- другая транзакция вставляет новую книгу
SELECT * FROM products WHERE category = 'books';
-- результат тот же, фантомов нет
COMMIT;

Большинство СУБД по умолчанию работают на уровне READ COMMITTED. PostgreSQL дополнительно предлагает уровень REPEATABLE READ через механизм MVCC (Multi-Version Concurrency Control) - каждая транзакция видит снимок данных на момент её начала.

Durability - надёжность фиксации

Надёжность гарантирует, что после успешного выполнения COMMIT данные сохранятся даже при сбое системы - отключении питания, аварии сервера или ошибке ОС.

Механизм надёжности основан на журнале упреждающей записи (WAL):

  1. Изменения записываются в журнал до применения к основным файлам данных
  2. Журнал синхронно сбрасывается на диск перед подтверждением COMMIT
  3. При сбое СУБД воспроизводит журнал и восстанавливает состояние
-- PostgreSQL: настройка уровня надёжности
ALTER SYSTEM SET synchronous_commit = 'on';  -- ждём подтверждения от диска
ALTER SYSTEM SET fsync = 'on';               -- принудительная запись на диск

Некоторые СУБД позволяют снизить надёжность ради скорости, отключая синхронную запись (synchronous_commit = off). В таком режиме данные могут потеряться при сбое в течение последних секунд, но вероятность потери фиксированных транзакций минимальна. Для критичных данных fsync отключать нельзя.

ACID в NoSQL и распределённых системах

Классические реляционные СУБД (PostgreSQL, MySQL, Oracle) полностью поддерживают ACID. В распределённых системах и NoSQL-базах ситуация сложнее.

Теорема CAP утверждает, что распределённая система не может одновременно гарантировать согласованность (Consistency), доступность (Availability) и устойчивость к разделению сети (Partition tolerance). При сбое сети приходится выбирать между согласованностью и доступностью.

Альтернатива ACID в распределённых системах - модель BASE:

  • Basically Available - система доступна, но без строгих гарантий
  • Soft state - состояние может меняться со временем
  • Eventual consistency - согласованность достигается в конечном счёте

Современные СУБД ищут компромисс: MongoDB добавил мультидокументные транзакции с уровнем SNAPSHOT, CockroachDB обеспечивает SERIALIZABLE через распределённый протокол.

Выбирайте уровень изоляции исходя из требований приложения: для большинства веб-приложений READ COMMITTED достаточен, для финансовых систем - SERIALIZABLE или REPEATABLE READ.

Оптимистичная и пессимистичная блокировка

Изолированность можно реализовать двумя принципиально разными способами.

Пессимистичная блокировка - транзакция захватывает блокировки при чтении данных, не давая другим транзакциям их изменить:

BEGIN;
SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
-- другие транзакции не смогут изменить эту строку
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;

Оптимистичная блокировка - транзакция читает данные без блокировок, но при записи проверяет, не изменились ли они. Реализуется через поле версии:

-- Читаем с версией
SELECT id, balance, version FROM accounts WHERE id = 1;
-- version = 5, balance = 1000

-- Обновляем только если версия не изменилась
UPDATE accounts
SET balance = 900, version = version + 1
WHERE id = 1 AND version = 5;

-- Если 0 строк обновлено - конфликт, повторяем транзакцию

Оптимистичная блокировка эффективна при низкой конкурентности - транзакции редко конфликтуют. Пессимистичная - при высокой вероятности конфликта, но создаёт риск взаимных блокировок (deadlock).

Взаимные блокировки и их обнаружение

Deadlock возникает, когда две транзакции ждут ресурсов друг друга:

  • Транзакция A заблокировала строку 1 и ждёт строку 2
  • Транзакция B заблокировала строку 2 и ждёт строку 1

СУБД обнаруживают deadlock через граф ожидания и автоматически прерывают одну из транзакций. Минимизировать deadlock помогают:

  • Единый порядок захвата ресурсов во всех транзакциях
  • Короткие транзакции с минимальным временем удержания блокировок
  • Использование NOWAIT или SKIP LOCKED для неблокирующих операций

Частые ошибки при работе с транзакциями

  • Длинные транзакции - транзакция удерживает блокировки, снижает параллелизм и увеличивает WAL. Держите транзакции короткими.
  • Транзакции с сетевыми вызовами - никогда не делайте HTTP-запросы внутри транзакции: сетевая задержка удерживает блокировки на секунды.
  • Autocommit в цикле - вставка тысяч строк через INSERT в цикле без явной транзакции создаёт отдельную транзакцию на каждую строку. Оберните в один BEGIN/COMMIT.
  • Игнорирование уровня изоляции - выбор уровня по умолчанию без анализа аномалий приводит к скрытым ошибкам в финансовой логике.
  • Отсутствие обработки rollback - код должен корректно реагировать на ошибки транзакций и повторять их при необходимости.

FAQ

Что такое savepoint в транзакции? Savepoint позволяет сохранить промежуточное состояние транзакции и откатиться к нему частично, не отменяя всю транзакцию. Команда SAVEPOINT имя создаёт точку, ROLLBACK TO имя откатывает к ней, RELEASE имя удаляет. Удобно для сложных транзакций с несколькими логическими этапами.

Чем отличается MVCC от блокировок? MVCC (Multi-Version Concurrency Control) хранит несколько версий каждой строки. Читающие транзакции видят снимок данных на момент своего начала и не блокируют записывающие. PostgreSQL и Oracle используют MVCC, что даёт высокий параллелизм без блокировок при чтении. MySQL InnoDB тоже использует MVCC.

Нужен ли ACID для микросервисов? В микросервисной архитектуре ACID в классическом виде недостижим - каждый сервис управляет своей БД. Используют паттерны Saga (распределённые транзакции через события) и Two-Phase Commit (2PC). Паттерн Outbox гарантирует атомарность публикации событий вместе с записью в БД.

Коротко

ACID - это четыре гарантии надёжности транзакций: Atomicity (всё или ничего), Consistency (соблюдение ограничений), Isolation (транзакции не мешают друг другу) и Durability (зафиксированные данные не теряются). Уровни изоляции (READ UNCOMMITTEDSERIALIZABLE) регулируют компромисс между корректностью и производительностью. Правильный выбор уровня изоляции, короткие транзакции и понимание механизмов блокировки - основа надёжного SQL-приложения.

Доверьте текст нейросети EssayAI

Открыть EssayAI

Бесплатно, на русском языке и без VPN

Читайте также