Как в PostgreSQL достигается уровень изоляции SERIALIZABLE?

Ответ

PostgreSQL для уровня изоляции SERIALIZABLE использует уникальный механизм под названием Serializable Snapshot Isolation (SSI).

В отличие от более простого Snapshot Isolation (SI), который используется в Oracle или в режиме REPEATABLE READ в самом PostgreSQL, SSI дополнительно обнаруживает и предотвращает аномалии типа write skew (перекос записи), которые могут нарушить строгую сериализуемость.

Как это работает:

  1. Снимок данных: Каждая транзакция работает со своим "снимком" данных, зафиксированным на момент её начала. Она не видит коммиты, сделанные другими транзакциями после её старта.
  2. Отслеживание зависимостей: PostgreSQL отслеживает зависимости "чтение-запись" между параллельными транзакциями. Если транзакция A читает данные, которые затем изменяет транзакция B (до коммита A), создается зависимость.
  3. Обнаружение циклов: Перед коммитом транзакция проверяет, не создала ли она "опасный" цикл зависимостей. Если две транзакции прочитали данные, на основе которых обе принимают решение об изменении других данных, может возникнуть цикл. В этом случае одна из транзакций будет прервана с ошибкой serialization failure.

Пример аномалии Write Skew, которую предотвращает SSI:

Представим, что есть правило: суммарный баланс на двух счетах не должен быть отрицательным.

-- Исходное состояние: account1.balance = 100, account2.balance = 100

-- Транзакция 1
BEGIN ISOLATION LEVEL SERIALIZABLE;
-- Проверяет, что после списания 150 общий баланс будет >= 0
-- (100 + 100) - 150 = 50. Ок.
SELECT balance FROM accounts WHERE id IN (1, 2); 

-- Транзакция 2
BEGIN ISOLATION LEVEL SERIALIZABLE;
-- Проверяет, что после списания 150 общий баланс будет >= 0
-- (100 + 100) - 150 = 50. Ок.
SELECT balance FROM accounts WHERE id IN (1, 2);

-- Транзакция 1
UPDATE accounts SET balance = balance - 150 WHERE id = 1;
COMMIT; -- Успешно

-- Транзакция 2
UPDATE accounts SET balance = balance - 150 WHERE id = 2;
COMMIT; -- ОШИБКА: 40001: could not serialize access due to read/write dependencies among transactions

Без SSI обе транзакции завершились бы успешно, и итоговый баланс стал бы (100-150) + (100-150) = -100, нарушив бизнес-правило. SSI обнаруживает этот конфликт и откатывает вторую транзакцию. Приложение должно быть готово перехватывать такие ошибки и повторять транзакцию.