# xmin, xmax и правила видимости _MVCC и видимость · PostgreSQL Knowledge Base_ **TL;DR:** У каждой версии строки два транзакционных штампа: xmin (кто вставил) и xmax (кто пометил устаревшей, 0 - если жива). Версия видна транзакции, если её xmin уже зафиксирован и попадает в снимок, а xmax либо пуст, либо ещё не зафиксирован, либо не виден снимку. По этим двум числам строится вся видимость. Видимость строки в PostgreSQL - это не флаг «удалена / не удалена», а вывод из двух чисел. Каждая версия (см. [tuple-header](/courses/postgres/kb/tuple-header.md)) хранит: - `xmin` - идентификатор транзакции, которая её **вставила**; - `xmax` - идентификатор транзакции, которая её **пометила устаревшей** (удалила, обновила или заблокировала). Если версия жива, `xmax = 0`. Прочитать их можно прямо как системные колонки: ```sql SELECT xmin, xmax, ctid, * FROM mv; -- xmin | xmax | ctid | id | note -- ------+------+-------+----+------ -- 786 | 0 | (0,1) | 1 | a ``` ## Правило видимости Транзакция смотрит на версию через свой снимок (см. [snapshot](/courses/postgres/kb/snapshot.md)) и решает по двум вопросам: 1. **Появилась ли версия в прошлом?** `xmin` должен быть зафиксирован и виден снимку: транзакция-создатель уже закоммитилась и не идёт прямо сейчас. 2. **Не исчезла ли версия в прошлом?** `xmax` должен быть либо 0, либо указывать на транзакцию, которая ещё не зафиксирована или не видна снимку. Тогда удаление «ещё не случилось» с точки зрения этой транзакции. Если оба условия выполнены - версия видна. Коротко: **видно то, что уже вставлено и ещё не удалено в твоём срезе времени.** ## Откуда берётся статус «зафиксирован» Сами по себе `xmin` и `xmax` - просто номера. Зафиксирована транзакция или откатилась, хранится отдельно, в clog. При первой проверке результат кэшируется в строке подсказками фиксации, чтобы не лазить в clog каждый раз. Это разбирает [clog-hint-bits](/courses/postgres/kb/clog-hint-bits.md). ## xmax не всегда значит «удалена» Тонкость: `xmax` ставится не только при удалении, но и когда строку просто **блокируют** - например, `SELECT ... FOR UPDATE` или вставка дочерней строки с внешним ключом. Такой `xmax` помечен как «только блокировка», и строка остаётся живой. Поэтому ненулевой `xmax` у видимой строки - не обязательно ошибка: ```sql -- у строк flights xmax заполнен: их «придержала» вставка в tickets по FK, -- но сами строки живы SELECT xmin, xmax FROM flights LIMIT 1; ``` ## Зачем это знать Правило видимости - это фундамент, на котором стоят уровни изоляции (см. [isolation-levels](/courses/postgres/kb/isolation-levels.md)). Один и тот же набор версий на диске разные транзакции видят по-разному, потому что у каждой свой снимок, а правило одно. Когда понимаешь, что видимость вычисляется, а не хранится, перестают удивлять и «пропавшие» в одной сессии строки, и мёртвые версии, которые держит долгая транзакция (см. [transaction-horizon](/courses/postgres/kb/transaction-horizon.md)). ## Команды ```sql SELECT xmin, xmax, ctid, * FROM mv; ``` Транзакционные штампы каждой видимой версии строки ```sql SELECT t_xmin, t_xmax, t_infomask::bit(16) FROM heap_page_items(get_raw_page('mv', 0)); ``` Те же поля на уровне страницы плюс флаги (в т.ч. блокировка vs удаление) ```sql SELECT pg_current_snapshot(); ``` Текущий снимок - рамка, относительно которой решается видимость ## См. также - [Снимок данных (snapshot)](/courses/postgres/kb/snapshot.md) - [Заголовок кортежа](/courses/postgres/kb/tuple-header.md) - [clog и подсказки фиксации (hint bits)](/courses/postgres/kb/clog-hint-bits.md) - [Уровни изоляции в PostgreSQL](/courses/postgres/kb/isolation-levels.md) - [Горизонт транзакции](/courses/postgres/kb/transaction-horizon.md)