Ответ
PostgreSQL для уровня изоляции SERIALIZABLE
использует уникальный механизм под названием Serializable Snapshot Isolation (SSI).
В отличие от более простого Snapshot Isolation (SI), который используется в Oracle или в режиме REPEATABLE READ
в самом PostgreSQL, SSI дополнительно обнаруживает и предотвращает аномалии типа write skew (перекос записи), которые могут нарушить строгую сериализуемость.
Как это работает:
- Снимок данных: Каждая транзакция работает со своим "снимком" данных, зафиксированным на момент её начала. Она не видит коммиты, сделанные другими транзакциями после её старта.
- Отслеживание зависимостей: PostgreSQL отслеживает зависимости "чтение-запись" между параллельными транзакциями. Если транзакция A читает данные, которые затем изменяет транзакция B (до коммита A), создается зависимость.
- Обнаружение циклов: Перед коммитом транзакция проверяет, не создала ли она "опасный" цикл зависимостей. Если две транзакции прочитали данные, на основе которых обе принимают решение об изменении других данных, может возникнуть цикл. В этом случае одна из транзакций будет прервана с ошибкой
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 обнаруживает этот конфликт и откатывает вторую транзакцию. Приложение должно быть готово перехватывать такие ошибки и повторять транзакцию.