linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
Intro
Lessons
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Главы
  • How it worksскоро
  • Уроки
  • База знаний
  • Собеседование
Часть II — Многоверсионность (MVCC)

$ глава 11 · 55 минут

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

В прошлой главе мы выяснили: уровень изоляции - это политика «когда брать снимок». Теперь развернём её в полную картину. Три уровня, у каждого свой набор аномалий, которые он пропускает или отсекает. И всё это - не таблица для заучивания, а наблюдаемое поведение, которое мы воспроизведём на двух сессиях.

В лабе ты увидишь неповторяющееся чтение на уровне по умолчанию, заморозишь картину на Repeatable Read, а на Serializable поймаешь настоящую ошибку сериализации - и поймёшь, почему она спасает данные.

11.1 Уровень изоляции - это политика снимка

Стандарт SQL описывает четыре уровня изоляции через аномалии, которые они запрещают. PostgreSQL реализует их строже стандарта, и реально различимы три уровня. Отличает их одно - когда берётся снимок (см. snapshot):

  • Read Committed - новый снимок на каждый оператор;
  • Repeatable Read - один снимок на всю транзакцию;
  • Serializable - тот же снимок плюс проверка зависимостей.

Грязного чтения (увидеть незакоммиченное чужое) в PostgreSQL не бывает ни на одном уровне - это особенность реализации. Полный разбор - в isolation-levels.

11.2 Какие аномалии где возможны

АномалияRead CommittedRepeatable ReadSerializable
грязное чтениенетнетнет
неповторяющееся чтениеданетнет
фантомное чтениеданетнет
аномалия сериализациидаданет

Чем выше уровень, тем больше аномалий отсечено и тем дороже. Неповторяющееся и фантомное чтение уходят уже на Repeatable Read, потому что снимок заморожен. А самая хитрая - аномалия сериализации - проходит даже на Repeatable Read и ловится только на Serializable.

11.3 Неповторяющееся чтение на Read Committed

Воспроизведём аномалию на уровне по умолчанию. Сессия A читает бронь дважды, между чтениями сессия B её меняет и коммитит.

sql
-- сессия A (Read Committed по умолчанию)
SELECT total FROM bookings WHERE book_ref = '000002';   -- 102.00
-- сессия B
UPDATE bookings SET total = 500 WHERE book_ref = '000002';
COMMIT;
-- сессия A, повторное чтение в той же транзакции
SELECT total FROM bookings WHERE book_ref = '000002';   -- 500.00 !

Два одинаковых запроса в одной транзакции дали разный ответ. Это и есть неповторяющееся чтение: между ними сессия A взяла новый снимок (на Read Committed снимок берётся на каждый оператор) и увидела свежий коммит сессии B.

11.4 Repeatable Read замораживает картину

Тот же сценарий, но сессия A работает на Repeatable Read:

sql
-- сессия A
BEGIN ISOLATION LEVEL REPEATABLE READ;
SELECT total FROM bookings WHERE book_ref = '000002';   -- 102.00
-- сессия B меняет и коммитит, как раньше
-- сессия A, повторное чтение
SELECT total FROM bookings WHERE book_ref = '000002';   -- по-прежнему 102.00

Картина не дрогнула. Снимок взялся один раз в начале транзакции и держится до конца, поэтому коммит сессии B в него не просочился. Повторное чтение всегда даёт тот же ответ. За это и ценят Repeatable Read: отчёт на нём видит согласованный срез, что бы ни творилось вокруг.

11.5 Serializable ловит перекос записи

Repeatable Read убирает неповторяющиеся и фантомные чтения, но пропускает аномалию посложнее - перекос записи (write skew). Классика: двое дежурных, каждый снимает с дежурства себя, видя, что второй ещё на смене. По отдельности оба решения корректны, вместе - дежурных не осталось.

Воспроизведём на Serializable. Обе сессии видят, что на дежурстве двое, и каждая снимает своего:

sql
-- сессия A                          -- сессия B
BEGIN ISOLATION LEVEL SERIALIZABLE;  BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT count(*) FROM oncall          SELECT count(*) FROM oncall
  WHERE on_call;  -- 2                 WHERE on_call;  -- 2
UPDATE oncall SET on_call=false      UPDATE oncall SET on_call=false
  WHERE name='alice';                  WHERE name='bob';
COMMIT;  -- успех                     COMMIT;

Сессия A коммитится спокойно. А сессия B при коммите получает:

ERROR:  could not serialize access due to read/write dependencies among transactions
DETAIL:  Reason code: Canceled on identification as a pivot, during commit attempt.
HINT:  The transaction might succeed if retried.

Перекос пойман. В итоге на дежурстве остался один - данные целы. Serializable увидел, что обе транзакции читали то, что меняла другая, и пожертвовал второй. Как именно он это отслеживает (предикатные блокировки SSI) - в serializable-ssi.

11.6 Цена за строгость: ошибка 40001

На Repeatable Read и Serializable конфликт не приводит к ожиданию - транзакция откатывается с кодом 40001. Сообщения разные, причина общая: «эту параллельность нельзя свести к последовательному выполнению».

УровеньСообщение при конфликте
Repeatable Readcould not serialize access due to concurrent update
Serializablecould not serialize access due to read/write dependencies

Это штатный сигнал, а не сбой. Кто записал первым - выиграл, остальные получают 40001 и должны повторить транзакцию. Поэтому приложение на высоких уровнях изоляции обязано уметь повторять транзакцию по этому коду.

11.7 Подводный камень: высокий уровень без повтора

Главная ошибка с высокими уровнями изоляции - включить Serializable и забыть про повтор. Тогда первая же конкурентная нагрузка начнёт сыпать пользователям ошибки 40001 как фатальные, хотя их надо было молча повторить.

Serializable снимает с разработчика ручные блокировки SELECT FOR UPDATE и даёт писать код так, будто транзакции идут по очереди. Но взамен требует одного: цикла повтора по 40001. Без него высокий уровень изоляции из защиты превращается в источник случайных ошибок. Правило простое: повышаешь уровень - добавляй повтор.

Уроки в sandbox

lab-11.1. Аномалии на разных уровнях

Воспроизведи неповторяющееся чтение, заморозь его на Repeatable Read и поймай ошибку сериализации на Serializable. Перед каждым шагом предсказывай: повторится ли чтение, пройдёт ли коммит.

  1. В сессии A (Read Committed) прочитай SELECT total FROM bookings WHERE book_ref='000002'; и запомни значение.

  2. В сессии B измени и зафиксируй: UPDATE bookings SET total=500 WHERE book_ref='000002'; COMMIT;.

  3. Прочитай в сессии A ещё раз - значение изменилось: это неповторяющееся чтение на Read Committed.

  4. Создай таблицу дежурных: CREATE TABLE oncall(name text, on_call boolean); INSERT INTO oncall VALUES ('alice',true),('bob',true);.

  5. В обеих сессиях открой BEGIN ISOLATION LEVEL SERIALIZABLE;, прочитай SELECT count(*) FROM oncall WHERE on_call; (обе увидят 2), и сними каждая своего: A - alice, B - bob.

  6. Закоммить сессию A (пройдёт), затем сессию B - предскажи и поймай ошибку 40001; проверь, что на дежурстве остался ровно один.

sandbox с автопроверкой - открыть в песочнице

Резюме

  • PostgreSQL различает три уровня изоляции; грязного чтения нет ни на одном.
  • Read Committed берёт снимок на каждый оператор - возможны неповторяющееся и фантомное чтение.
  • Repeatable Read замораживает снимок на всю транзакцию - эти аномалии исчезают.
  • Serializable добавляет проверку зависимостей и ловит перекос записи, которого RR не видит.
  • Конфликт на высоких уровнях даёт не ожидание, а откат с кодом 40001 - сигнал повторить транзакцию.
  • Включив Serializable, обязательно добавь цикл повтора по 40001, иначе он сыплет ошибками.

Контрольные вопросы

  1. Почему в PostgreSQL не бывает грязного чтения даже на самом низком уровне?

    Показать ответ

    Потому что так устроена реализация многоверсионности: транзакция видит только зафиксированные версии. Незакоммиченную чужую версию правило видимости отвергает на любом уровне изоляции. Стандарт SQL допускает грязное чтение на Read Uncommitted, но в PostgreSQL этот уровень ведёт себя как Read Committed - грязного чтения не происходит.

  2. Чем отличается момент взятия снимка на Read Committed и Repeatable Read?

    Показать ответ

    На Read Committed новый снимок берётся на каждый оператор, поэтому повторное чтение в одной транзакции может увидеть свежие чужие коммиты - это и есть неповторяющееся чтение. На Repeatable Read снимок берётся один раз в начале транзакции и держится до конца, поэтому повторное чтение всегда даёт тот же результат. Разница в политике обновления снимка и определяет, какие аномалии возможны.

  3. Что такое перекос записи и почему Repeatable Read его не ловит?

    Показать ответ

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

  4. Что значит ошибка 40001 и как на неё реагировать?

    Показать ответ

    40001 - ошибка сериализации: PostgreSQL обнаружил, что данную параллельность нельзя свести к последовательному выполнению, и откатил одну из транзакций. Это не сбой, а штатный сигнал. Реагировать нужно повтором: приложение должно поймать код 40001 и выполнить транзакцию заново. На Repeatable Read это конфликт обновления, на Serializable - зависимость чтения-записи, но реакция одна.

  5. Какую обязанность накладывает переход на Serializable?

    Показать ответ

    Обязанность повторять транзакции по ошибке 40001. Serializable не блокирует и не ждёт - при опасной зависимости он откатывает транзакцию. Если приложение не ловит 40001 и не повторяет транзакцию, под конкурентной нагрузкой пользователи получат поток ошибок. Поэтому повышение уровня изоляции до Serializable всегда идёт в паре с циклом повтора.

← Предыдущая10-snapshotСледующая →12-clog-hint-bits
Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки