# clog и подсказки фиксации (hint bits) _MVCC и видимость · PostgreSQL Knowledge Base_ **TL;DR:** Зафиксирована транзакция или откатилась, хранит clog (каталог pg_xact) - всего два бита на транзакцию. Чтобы не дёргать clog при каждом чтении строки, результат проверки кэшируется в самой строке подсказками фиксации в t_infomask. Первый прочитавший строку ставит подсказку - и тем самым пачкает страницу, даже если это был обычный SELECT. Поля `xmin` и `xmax` (см. [xmin-xmax](/courses/postgres/kb/xmin-xmax.md)) - это просто номера транзакций. По ним нельзя узнать, чем транзакция кончилась: коммитом или откатом. Этот факт хранится отдельно. ## clog: два бита на транзакцию Статус каждой транзакции лежит в **clog** - каталоге `pg_xact/` внутри PGDATA. На транзакцию приходится всего два бита: | Биты | Статус | |---|---| | в процессе | ещё идёт | | committed | зафиксирована | | aborted | откатилась | | sub-committed | подтранзакция (промежуточно) | Два бита на транзакцию - это очень компактно: статусы миллионов транзакций умещаются в мегабайты, а горячая часть кэшируется в памяти. Когда правило видимости спрашивает «а `xmin` этой версии точно закоммитился?», ответ ищется именно в clog. ## Подсказки фиксации: не спрашивать дважды Лезть в clog на каждую строку при каждом чтении было бы дорого. Поэтому результат проверки кэшируется прямо в строке - **подсказками фиксации** (hint bits) в поле `t_infomask` (см. [tuple-header](/courses/postgres/kb/tuple-header.md)): ```mermaid flowchart LR R["Читаем версию строки"] --> H{"Подсказка
уже стоит?"} H -->|да| FAST["сразу знаем статус,
в clog не идём"] H -->|нет| C["спросить clog"] C --> SET["записать подсказку
в t_infomask"] SET --> FAST ``` Флаги `HEAP_XMIN_COMMITTED` / `HEAP_XMIN_INVALID` и парные для `xmax` означают «создатель/удалитель точно закоммитился / откатился». Стоит такой флаг - и clog трогать не надо. ## Подводный камень: SELECT, который пишет Вот неожиданность. Подсказку ставит **первый, кто прочитал строку** после того, как её транзакция завершилась. Установка подсказки меняет байты страницы, а значит, помечает её грязной - и страница будет записана на диск. Получается, что безобидный с виду `SELECT` сразу после массовой загрузки данных вызывает шквал записи: он расставляет подсказки по всем свежим строкам. ```sql SELECT t_infomask::bit(16) FROM heap_page_items(get_raw_page('mv', 0)); -- первый SELECT после коммита расставит hint bits; -- повторный покажет уже выставленные флаги ``` Поэтому «холодное» первое чтение только что загруженной таблицы бывает заметно дороже последующих - и удивляет тех, кто считает SELECT бесплатным по записи. Это не утечка и не баг, а отложенная работа по фиксации статуса. ## Команды ```sql SELECT t_infomask::bit(16) FROM heap_page_items(get_raw_page('mv', 0)); ``` Флаги строки, включая подсказки фиксации xmin/xmax ```sql SELECT (t_infomask & 256) <> 0 AS xmin_committed FROM heap_page_items(get_raw_page('mv', 0)); ``` Проверить бит HEAP_XMIN_COMMITTED напрямую ```sql SELECT pg_xact_status(pg_current_xact_id()); ``` Статус транзакции по clog: in progress / committed / aborted ## См. также - [xmin, xmax и правила видимости](/courses/postgres/kb/xmin-xmax.md) - [Заголовок кортежа](/courses/postgres/kb/tuple-header.md) - [Вложенные транзакции и savepoints](/courses/postgres/kb/subtransactions.md)