linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
  • PostgreSQL изнутри
    Страница и кортеж, MVCC, vacuum, WAL, планировщик и индексы
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
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
    →
  • PostgreSQL изнутри
    Страница и кортеж, MVCC, vacuum, WAL, планировщик и индексы
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Главы
  • How it works
  • Уроки
  • База знаний
  • Собеседование
Cluster

← все кластеры

Снимки, xmin/xmax, уровни изоляции

Как PostgreSQL даёт читателям и писателям не мешать друг другу: версии строк, снимок как набор чисел, clog и hint bits, и четыре уровня изоляции с их аномалиями. Здесь чаще всего проваливаются на собеседовании, потому что MVCC легко выучить на словах и трудно объяснить «как именно».

8 вопросов · ~40 мин чтения

Questions

На этой странице

  1. 01Зачем нужен MVCC? Что он даёт по сравнению с блокировкой на чтение?
  2. 02Как PostgreSQL решает, видна ли версия строки текущей транзакции?
  3. 03Что физически представляет собой снимок данных? Это копия?
  4. 04Какие уровни изоляции есть в PostgreSQL и какие аномалии каждый отсекает?
  5. 05Repeatable Read против Serializable: что именно ловит SSI?
  6. 06Почему UPDATE в PostgreSQL это фактически новая версия строки? Чем это аукается?
  7. 07Что такое clog и hint bits и зачем они нужны?
  8. 08Чем виртуальный xid отличается от настоящего и при чём тут подтранзакции?

#why-mvcc

juniorчасто

Зачем нужен MVCC? Что он даёт по сравнению с блокировкой на чтение?

Что отвечать

MVCC (multiversion concurrency control) держит несколько версий одной строки одновременно. Читатель видит снимок данных на момент начала запроса или транзакции и не ждёт писателей, а писатель не ждёт читателей. Главное правило: чтение не блокирует запись, запись не блокирует чтение. Цена - старые версии накапливаются как мусор, и его надо вычищать (этим занят vacuum). Альтернатива из старых СУБД - блокировать строку на чтение - даёт меньше мусора, но превращает конкурентную нагрузку в очередь.

Что хотят услышать

Кандидат должен: - сформулировать правило «читатели не блокируют писателей и наоборот» и понимать, что это и есть смысл многоверсионности - назвать обратную сторону: каждая старая версия живёт до очистки, поэтому MVCC и vacuum это одна тема - связать снимок с моментом времени: что увидит транзакция, зависит от уровня изоляции и от того, когда взят снимок - не путать MVCC с «отсутствием блокировок»: блокировки строк и отношений никуда не делись, MVCC снимает только конфликт чтение vs запись

Подводные камни

  • ✗ Сказать «в PostgreSQL нет блокировок благодаря MVCC» - блокировки есть, MVCC убирает лишь конфликт чтения и записи
  • ✗ Забыть про цену: версии копятся, и без vacuum таблица раздувается
  • ✗ Думать, что читатель всегда видит самые свежие данные - он видит свой снимок, а не последнее состояние

Follow-up

  • ? Почему долгая читающая транзакция мешает очистке мусора?
  • ? Что в PostgreSQL всё-таки блокирует, несмотря на MVCC?
  • ? Откуда берётся раздувание таблицы под нагрузкой из UPDATE?

Глубина в базе знаний

  • Зачем многоверсионность
  • Снимок данных (snapshot)
  • xmin, xmax и правила видимости
tags: mvcc, concurrency, snapshotbook: postgresql_internals-17.pdf:part1 isolation and mvcc

#xmin-xmax-visibility

intermediateчасто

Как PostgreSQL решает, видна ли версия строки текущей транзакции?

Что отвечать

У версии есть `xmin` (кто создал) и `xmax` (кто удалил или заблокировал). Транзакция берёт снимок: своё число, граница «всё до неё точно завершено» и список ещё идущих транзакций. Версия видна, если `xmin` завершилась успешно и попадает в прошлое снимка, а `xmax` либо пуст, либо принадлежит ещё не завершённой или откатанной транзакции. Статус транзакции (закоммичена/откатана) лежит в clog, но проверять его каждый раз дорого, поэтому первый, кто посмотрел, выставляет hint bits в `t_infomask` - дальше ответ берётся из самой строки.

Что хотят услышать

Senior должен: - описать снимок как тройку: `xmin` снимка, `xmax` снимка и список активных xid, а не как копию данных - провести версию через правило видимости: создатель закоммичен и в прошлом, удалитель отсутствует или невидим - объяснить роль clog и hint bits: clog хранит исход транзакции, hint bits кешируют его в строке, убирая повторные обращения - понимать, что один и тот же ряд может иметь несколько видимых разным транзакциям версий одновременно

Подводные камни

  • ✗ Считать снимок копией таблицы - это несколько чисел и список xid плюс правила видимости
  • ✗ Думать, что статус транзакции хранится в строке изначально - он в clog, в строку попадает позже через hint bits
  • ✗ Забыть, что `xmax` бывает у живой строки - её просто кто-то заблокировал `FOR UPDATE`

Follow-up

  • ? Из чего состоит снимок, который видит `pg_current_snapshot()`?
  • ? Зачем нужны hint bits, если статус и так есть в clog?
  • ? Может ли строка с непустым `xmax` быть видимой? Когда?

Глубина в базе знаний

  • xmin, xmax и правила видимости
  • Снимок данных (snapshot)
  • clog и подсказки фиксации (hint bits)
tags: mvcc, visibility, snapshotbook: postgresql_internals-17.pdf:ch4 snapshots

#snapshot-is-not-copy

intermediateчасто

Что физически представляет собой снимок данных? Это копия?

Что отвечать

Снимок - не копия данных, а маленький набор чисел: граница, ниже которой все транзакции уже завершены (`xmin` снимка), граница, выше которой все ещё не начинались (`xmax` снимка), и явный список xid, которые были активны в момент взятия снимка. Видимость любой версии строки вычисляется по этим числам на лету. Поэтому снимок дёшев - его можно взять мгновенно и даже экспортировать в другую сессию (`pg_export_snapshot`), чтобы `pg_dump` параллельными процессами читал согласованную картину.

Что хотят услышать

Senior должен: - развенчать наивную модель «снимок копирует строки»: копируются числа, а не данные - объяснить, как из снимка получается видимость конкретной версии - привести экспорт снимка как практику: параллельный `pg_dump`, согласованное чтение несколькими бэкендами - связать снимок с горизонтом: пока снимок жив, версии в его прошлом нельзя считать мусором, и vacuum их не тронет

Подводные камни

  • ✗ Сказать «снимок это MVCC-копия таблицы» - это несколько чисел плюс список активных xid
  • ✗ Думать, что снимок дорогой - он почти бесплатный, дорого его долго держать
  • ✗ Не связать снимок с горизонтом очистки - именно долгий снимок держит мусор

Follow-up

  • ? Зачем `pg_export_snapshot` параллельному `pg_dump`?
  • ? Почему долгоживущий снимок откладывает очистку?
  • ? Чем снимок на уровне Read Committed отличается от снимка Repeatable Read?

Глубина в базе знаний

  • Снимок данных (snapshot)
  • Экспорт снимка между сессиями
  • xmin, xmax и правила видимости
tags: mvcc, snapshot, isolationbook: postgresql_internals-17.pdf:ch4 snapshots

#isolation-levels-pg

intermediateчасто

Какие уровни изоляции есть в PostgreSQL и какие аномалии каждый отсекает?

Что отвечать

Стандарт описывает четыре уровня, PostgreSQL реализует три различимых: Read Committed (дефолт), Repeatable Read и Serializable; запрошенный Read Uncommitted ведёт себя как Read Committed, грязного чтения тут не бывает. Read Committed берёт новый снимок на каждый оператор - возможны non-repeatable read и phantom. Repeatable Read берёт один снимок на всю транзакцию - повторные чтения стабильны, но возможна аномалия записи (write skew). Serializable добавляет отслеживание зависимостей через предикатные блокировки (SSI) и гарантирует результат, как при последовательном выполнении.

Что хотят услышать

Senior должен: - назвать три реальных уровня и сказать, что Read Uncommitted у PG равен Read Committed - связать уровень с моментом снимка: на оператор (RC) или на транзакцию (RR/Serializable) - перечислить аномалии по нарастающей: non-repeatable read, phantom, write skew - и какой уровень какую отсекает - знать, что Serializable может откатить транзакцию с ошибкой сериализации, и приложение должно уметь её повторить

Подводные камни

  • ✗ Сказать «в PG есть грязное чтение на Read Uncommitted» - его нет, уровень схлопывается в Read Committed
  • ✗ Считать, что Repeatable Read ловит write skew - не ловит, для этого нужен Serializable
  • ✗ Забыть про ретрай: Serializable бросает `serialization_failure`, и транзакцию надо повторить

Follow-up

  • ? Чем снимок Read Committed отличается от Repeatable Read по времени взятия?
  • ? Что такое write skew и почему его не видит Repeatable Read?
  • ? Как приложение должно реагировать на ошибку сериализации?

Глубина в базе знаний

  • Уровни изоляции в PostgreSQL
  • Снимок данных (snapshot)
  • Serializable и SSI
tags: mvcc, isolation, serializablebook: postgresql_internals-17.pdf:ch2 isolation · postgresql_internals-17.pdf:ch4 snapshots

#repeatable-read-vs-serializable

seniorиногда

Repeatable Read против Serializable: что именно ловит SSI?

Что отвечать

Repeatable Read даёт стабильный снимок: внутри транзакции данные не меняются под ногами. Но два таких снимка могут разойтись на записи: каждая транзакция читает одно состояние, обе пишут, и итог невозможен ни при каком последовательном порядке - это write skew. Serializable добавляет SSI (serializable snapshot isolation): сервер отслеживает опасные циклы зависимостей чтение-запись через предикатные (SIRead) блокировки и откатывает одну из транзакций с ошибкой сериализации. Гарантия - результат эквивалентен какому-то последовательному порядку.

Что хотят услышать

Senior должен: - привести классический write skew (например, двое снимают последнюю бронь, каждый видит её свободной) и показать, почему RR его пропускает - объяснить SSI как отслеживание зависимостей, а не как блокировку всего на чтение - знать про SIReadLock: лёгкие предикатные блокировки, которые не ждут, а лишь помечают прочитанное - понимать цену: Serializable даёт ложные срабатывания и требует ретраев, зато снимает целый класс гонок без ручных блокировок

Подводные камни

  • ✗ Думать, что Serializable блокирует строки на чтение - он отслеживает зависимости, а не держит блокировки
  • ✗ Считать, что Repeatable Read достаточно для инвариантов между строками - write skew пройдёт
  • ✗ Забыть про ретрай-цикл: без него Serializable просто заваливает транзакции ошибками

Follow-up

  • ? Приведи пример write skew, который видит Serializable и пропускает Repeatable Read
  • ? Что такое SIReadLock и почему она не вызывает ожиданий?
  • ? Почему Serializable иногда откатывает транзакцию, хотя данные не пересекались по строкам?

Глубина в базе знаний

  • Serializable и SSI
  • Уровни изоляции в PostgreSQL
  • Лёгкие, spin- и предикатные блокировки
tags: mvcc, serializable, ssibook: postgresql_internals-17.pdf:part3 locks

#update-is-delete-insert

intermediateчасто

Почему UPDATE в PostgreSQL это фактически новая версия строки? Чем это аукается?

Что отвечать

UPDATE не правит строку на месте: он помечает старую версию через `xmax` и кладёт новую версию с новым `xmin`. Старая живёт, пока её видит хоть один снимок, потом её заберёт vacuum. Отсюда два следствия. Первое - раздувание: интенсивные UPDATE плодят мёртвые версии быстрее, чем их чистят. Второе - индексы: по умолчанию новая версия требует новых записей во всех индексах таблицы. Спасает HOT-обновление - если ни одна проиндексированная колонка не менялась и в странице есть место, новая версия остаётся в той же странице без правки индексов.

Что хотят услышать

Senior должен: - описать UPDATE как delete-плюс-insert на уровне версий с пометкой `xmax` у старой - связать это с раздуванием и нагрузкой на vacuum - объяснить HOT: условие (не меняются индексируемые колонки, есть место в странице) и выгоду (без новых записей в индексы) - дать практику: `fillfactor` ниже 100 оставляет место под HOT, а частые UPDATE индексируемых колонок HOT убивают

Подводные камни

  • ✗ Сказать «UPDATE меняет строку на месте» - он создаёт новую версию, старая остаётся до очистки
  • ✗ Думать, что HOT работает всегда - он отваливается, если меняется индексируемая колонка или странице не хватает места
  • ✗ Игнорировать `fillfactor`: на таблице с тяжёлыми UPDATE он напрямую влияет на долю HOT

Follow-up

  • ? При каких условиях UPDATE пройдёт как HOT?
  • ? Зачем снижать `fillfactor` для таблицы с частыми обновлениями?
  • ? Почему UPDATE одной неиндексированной колонки дешевле, чем индексированной?

Глубина в базе знаний

  • xmin, xmax и правила видимости
  • HOT-обновления и fillfactor
  • Внутристраничная очистка (prune)
tags: mvcc, update, hotbook: postgresql_internals-17.pdf:ch5 hot updates

#clog-hint-bits

seniorиногда

Что такое clog и hint bits и зачем они нужны?

Что отвечать

В версии строки записан только номер транзакции-создателя, но не её исход. Закоммичена ли транзакция `xmin` или откатана, хранит clog (commit log, каталог `pg_xact`) - по два бита на транзакцию. Проверять clog при каждом чтении дорого, поэтому первый, кто определил исход, ставит в `t_infomask` строки hint bits: «xmin закоммичена» или «откатана». Дальше видимость считается без похода в clog. Побочный эффект: первый SELECT после массовой вставки «грязнит» страницы, проставляя hint bits, и порождает запись на диск, хотя данные не менялись.

Что хотят услышать

Senior должен: - разделить роли: clog хранит исход транзакции, hint bits кешируют его в самой строке - объяснить, почему первое чтение после большой загрузки внезапно пишет на диск (выставление hint bits грязнит страницы) - связать с производительностью «холодных» таблиц и с тем, почему повторный прогон того же запроса быстрее - понимать, что clog тоже подрезается: после заморозки старые записи больше не нужны и удаляются

Подводные камни

  • ✗ Думать, что исход транзакции лежит в строке сразу - сначала только в clog, в строку он попадает позже
  • ✗ Удивляться записи на диск от `SELECT` - это hint bits, а не изменение данных
  • ✗ Не знать, что clog ограничен и подрезается заморозкой - отсюда часть смысла freeze

Follow-up

  • ? Почему первый `SELECT` после `COPY` миллиона строк пишет на диск?
  • ? Где физически лежит clog и сколько бит на транзакцию он тратит?
  • ? Как заморозка позволяет подрезать clog?

Глубина в базе знаний

  • clog и подсказки фиксации (hint bits)
  • xmin, xmax и правила видимости
  • Заморозка и relfrozenxid
tags: mvcc, clog, hint-bitsbook: postgresql_internals-17.pdf:ch4 snapshots

#virtual-vs-real-xid

seniorредко

Чем виртуальный xid отличается от настоящего и при чём тут подтранзакции?

Что отвечать

Настоящий номер транзакции (xid) - дефицитный 32-битный ресурс, его жаль тратить на транзакции, которые ничего не пишут. Поэтому пока транзакция только читает, ей выдают виртуальный xid (пара: номер бэкенда плюс локальный счётчик), а настоящий присваивают лениво, при первой записи. Подтранзакции (savepoint, блок с обработкой исключения в PL/pgSQL) тоже получают свои xid; их соответствие родителю хранит `pg_subtrans`. Откат к savepoint помечает версии подтранзакции невидимыми, не трогая родителя.

Что хотят услышать

Senior должен: - объяснить, зачем нужен виртуальный xid: экономия 32-битного счётчика и снижение давления на wraparound от читающих транзакций - сказать, что настоящий xid выдаётся лениво при первой записи - описать подтранзакции: savepoint и блоки исключений плодят дочерние xid, связь с родителем в `pg_subtrans` - предупредить про антипаттерн: тысячи savepoint или исключений в цикле раздувают `pg_subtrans` и бьют по производительности

Подводные камни

  • ✗ Думать, что каждая транзакция сразу ест номер xid - читающая обходится виртуальным
  • ✗ Не знать, что блок `BEGIN ... EXCEPTION` в PL/pgSQL это подтранзакция со своим xid
  • ✗ Игнорировать стоимость массовых savepoint - переполнение кеша подтранзакций бьёт по скорости

Follow-up

  • ? Когда читающая транзакция получит настоящий xid?
  • ? Почему цикл с `EXCEPTION` на каждой итерации может просесть по скорости?
  • ? Что хранит `pg_subtrans` и зачем?

Глубина в базе знаний

  • Виртуальные и реальные xid
  • Вложенные транзакции и savepoints
  • xmin, xmax и правила видимости
tags: mvcc, xid, subtransactionsbook: postgresql_internals-17.pdf:ch4 snapshots
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.