# 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)