open atlas
↑ К треку
SQL и PostgreSQL вглубь SQL · 07 · 02

Уровни изоляции в Postgres

READ COMMITTED, REPEATABLE READ и SERIALIZABLE — нарастающие обещания о том, какие аномалии видят конкурентные транзакции. Только SERIALIZABLE останавливает write skew — и заставляет писать retry-логику.

SQL Senior ◷ 16 min
Уровень
ОсновыJuniorMiddleSenior

Система дежурств в больнице держала одно правило: хотя бы один врач должен оставаться на дежурстве. Два врача, оба на дежурстве, оба нажали «уйти с дежурства» в одну секунду. Каждая транзакция прочитала «на дежурстве 2, нормально» и удалила себя. Итог: ноль дежурных — правило, которое уважала каждая отдельная транзакция, сломано их чередованием. Ни одного бага в запросах; баг был в уровне изоляции. У этой аномалии есть имя — write skew — и ровно один уровень её останавливает.

Уровни как лестница обещаний

Выбирая уровень изоляции, ты не настраиваешь скорость — ты выбираешь класс багов, которые принимаешь. Слишком низкий уровень — тихая порча данных; слишком высокий — пишешь retry-логику. Точное знание границы каждого уровня и отличает сеньора, который предотвращает инциденты, от того, кто потом их отлаживает.

Уровень изоляции — не ручка производительности; это контракт о том, какие аномалии позволено наблюдать конкурентным транзакциям. Postgres предлагает три рабочих уровня (он принимает READ UNCOMMITTED, но трактует его как READ COMMITTED — грязные чтения в Postgres невозможны никогда).

  • READ COMMITTED (по умолчанию) — ты видишь только закоммиченные данные, но каждый оператор берёт свежий снимок в начале оператора. Так что внутри одной транзакции два SELECT одной строки могут вернуть разные значения, если другая транзакция закоммитила между ними. Предотвращает: грязные чтения. Допускает: non-repeatable read, фантомы, lost update, write skew.
  • REPEATABLE READ — транзакция берёт один снимок на первом операторе и использует его всю транзакцию. Каждое чтение видит базу на тот момент. Предотвращает: грязные чтения, non-repeatable read и (в Postgres) фантомы. Допускает: write skew. Запись, конфликтующая с коммитом другой транзакции, падает с serialization error.
  • SERIALIZABLE — ведёт себя так, будто каждая транзакция шла одна за другой в некотором последовательном порядке. Реализован через Serializable Snapshot Isolation (SSI): отслеживает read/write-зависимости и прерывает транзакцию, чей коммит создал бы несериализуемое расписание. Предотвращает: всё, включая write skew. Цена: больше serialization failure, которые ты обязан повторять.

Снимок на оператор против снимка на транзакцию

Самое важное различие двух распространённых уровней — когда берётся снимок.

-- READ COMMITTED: каждый оператор переснимает.
BEGIN;  -- уровень по умолчанию
SELECT qty FROM inventory WHERE sku = 'A';   -- видит 10
-- ... другая сессия коммитит qty = 3 здесь ...
SELECT qty FROM inventory WHERE sku = 'A';   -- теперь видит 3  (non-repeatable read)
COMMIT;
-- REPEATABLE READ: один снимок на всю транзакцию.
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT qty FROM inventory WHERE sku = 'A';   -- видит 10
-- ... другая сессия коммитит qty = 3 здесь ...
SELECT qty FROM inventory WHERE sku = 'A';   -- всё ещё видит 10  (стабильное чтение)
COMMIT;

READ COMMITTED правилен для коротких OLTP-операторов, где нужны самые свежие закоммиченные данные. REPEATABLE READ правилен для отчёта или многооператорного чтения, которое должно быть внутренне согласованным — баланс, который не сдвигается под тобой посреди запроса.

Внутренности — как снимки, xmin/xmax и видимость работают на самом деле — это глава MVCC трека databases; здесь мы остаёмся на уровне SQL-контракта. Смотри databases/04-mvcc-isolation/03-hot-updates-and-isolation-levels для механизма версий строк и databases/04-mvcc-isolation/06-ssi-and-production-tuning про то, как SSI отслеживает конфликты.

Write skew и почему его ловит только SERIALIZABLE

Write skew — аномалия, удивляющая сеньоров. Две транзакции читают пересекающийся набор строк, каждая проверяет инвариант, который сейчас держится, затем каждая пишет другую строку. Ни одна запись не конфликтует с другой (разные строки), так что проверка «первый-обновивший-выигрывает» REPEATABLE READ не срабатывает — но вместе они ломают инвариант.

-- Инвариант дежурства, защищённый через SERIALIZABLE:
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT count(*) FROM doctors WHERE on_call = true;  -- должно остаться >= 1
-- приложение проверяет count >= 2 перед тем как разрешить это:
UPDATE doctors SET on_call = false WHERE id = 17;
COMMIT;   -- если конкурентная txn сломала сериализуемость, тут поднимется 40001

На SERIALIZABLE одна из двух конкурентных транзакций коммитится; другая получает ERROR: could not serialize access due to read/write dependencies among transactions (SQLSTATE 40001). Твоё приложение обязано ловить 40001 и повторять всю транзакцию. На повторе она перечитывает count = 1 и корректно отказывает. Без retry-логики SERIALIZABLE просто превращает тихую порчу данных в видимые ошибки, которые видят пользователи.

Граничные случаи

SERIALIZABLE не бесплатен и не магия. SSI ведёт бухгалтерию predicate-блокировок (SIReadLocks) на каждое чтение, что стоит памяти и под нагрузкой может эскалировать с гранулярности строки на страницу и на отношение, повышая ложноположительные аборты. Продакшн-правило: используй SERIALIZABLE для конкретных транзакций, несущих кросс-строчный инвариант (бронирование, резерв склада, правила дежурства), держи вокруг них retry-логику с ограниченным экспоненциальным backoff, а остальной трафик держи на READ COMMITTED. Резервирование строк явной блокировкой SELECT ... FOR UPDATE часто дешевле и предсказуемее — следующий урок.

Викторина

Транзакция на REPEATABLE READ делает два одинаковых SELECT inventory.qty для одного sku, а другая сессия коммитит изменение между ними. Что вернёт второй SELECT?

Викторина

Нужно гарантировать «хотя бы одна строка набора остаётся true» через конкурентные транзакции, каждая из которых меняет другую строку. Какой уровень и что нужно добавить?

Закончи аналогию

Заполни пропуск: аномалия, где две транзакции читают пересекающийся набор, каждая проверяет держащийся инвариант, затем каждая пишет другую строку и вместе ломают инвариант, называется write _______.

Вспомните перед уходом
  1. 01
    В чём ключевое поведенческое различие READ COMMITTED и REPEATABLE READ?
  2. 02
    Что такое write skew и какой уровень изоляции нужен, чтобы его предотвратить?
  3. 03
    Если перевести транзакцию на SERIALIZABLE, какую новую обязанность берёт код приложения?
Итог

Уровень изоляции — контракт о том, какие аномалии могут наблюдать конкурентные транзакции, а не настройка скорости. READ COMMITTED, умолчание Postgres, берёт свежий снимок на оператор: блокирует грязные чтения, но допускает non-repeatable read, фантомы, lost update и write skew. REPEATABLE READ берёт один снимок на всю транзакцию, давая стабильные чтения и (в Postgres) отсутствие фантомов, но всё ещё допускает write skew, потому что две транзакции, пишущие разные строки, никогда не конфликтуют. SERIALIZABLE использует Serializable Snapshot Isolation, чтобы вести себя как поочерёдное выполнение; это единственный уровень, предотвращающий write skew, ценой большего числа serialization failure (SQLSTATE 40001), которые приложение обязано ловить и повторять. Продакшн-дисциплина: держи основной трафик на READ COMMITTED, поднимай до SERIALIZABLE только транзакции с кросс-строчным инвариантом и retry-логикой, либо резервируй строки явно блокировками, которые узнаешь дальше. MVCC-машинерия за всем этим — снимки, версии строк, predicate-блокировки SSI — живёт в главе MVCC трека databases; этот урок — обещание на уровне SQL. Теперь, когда встретишь баг «два конкурентных обновления нарушили инвариант, который ни одно из них по отдельности не нарушило», ты потянешься к SERIALIZABLE с retry-циклом, а не к очередной строке-блокировке.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 7 завершено
Связанные уроки
опирается на

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.