Уровни изоляции и аномалии: dirty read, non-repeatable, phantom, serialization

↑ 2.1 PostgreSQL: индексы, транзакции, оптимизация · ← Предыдущая · → Следующая

Обязательно~3 мин чтенияУровень: middle

Зачем это на собесе

Один из самых частых вопросов: аномалии и что даёт каждый уровень в PostgreSQL.

Аномалии

АномалияОписание
Dirty readчтение незакоммиченных данных другой транзакции
Non-repeatable readповторное чтение той же строки даёт другое значение (её изменила и закоммитила другая транзакция)
Phantom readповторный запрос по условию возвращает другой набор строк
Lost updateдве транзакции читают, затем обе пишут: одно обновление теряется
Write skewобе транзакции читают пересекающиеся данные и пишут в разные строки, нарушая общее условие
Serialization anomalyрезультат невозможен ни при каком последовательном порядке

Уровни по стандарту SQL

УровеньDirtyNon-repeatablePhantom
Read Uncommittedвозможновозможновозможно
Read Committedнетвозможновозможно
Repeatable Readнетнетвозможно
Serializableнетнетнет

PostgreSQL

УровеньОсобенности
Read Committed (по умолчанию)каждый оператор видит снимок на момент своего начала; Read Uncommitted работает как Read Committed
Repeatable Readснимок фиксируется на первом операторе транзакции; (snapshot isolation) фантомы не возникают, но возможен write skew; при конфликте обновления — ошибка could not serialize access
SerializableSSI (Serializable Snapshot Isolation): отслеживает опасные зависимости, при риске аномалии — ошибка 40001, транзакцию нужно повторить
BEGIN ISOLATION LEVEL REPEATABLE READ;
-- или
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

Пример: lost update и как избежать

-- небезопасно (read-modify-write в приложении)
SELECT balance FROM accounts WHERE id = 1;     -- 100
-- приложение считает 100 - 30 = 70
UPDATE accounts SET balance = 70 WHERE id = 1; -- перезаписывает чужое изменение
 
-- решения
UPDATE accounts SET balance = balance - 30 WHERE id = 1;          -- атомарно на сервере
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;            -- пессимистическая блокировка
UPDATE accounts SET balance = ?, version = version + 1
 WHERE id = 1 AND version = ?;                                    -- оптимистичная (проверка version)

Write skew (пример)

Правило: в смене должен остаться хотя бы один врач. Два врача одновременно проверяют «сейчас двое дежурных», оба уходят: осталось ноль. Repeatable Read не спасёт; помогает Serializable или блокировка (SELECT ... FOR UPDATE по набору, advisory lock).

Выбор уровня

  • Read Committed — большинство приложений, с явными блокировками и атомарными обновлениями там, где нужно;
  • Repeatable Read — согласованные отчёты (снимок на одну транзакцию), готовность к retry;
  • Serializable — сложные инварианты; обязательно retry-логика; чуть выше накладные расходы.

Вопросы с ответами