Поля xmin и xmax (см. xmin-xmax) - это просто номера транзакций.
По ним нельзя узнать, чем транзакция кончилась: коммитом или откатом. Этот
факт хранится отдельно.
clog: два бита на транзакцию
Статус каждой транзакции лежит в clog - каталоге pg_xact/ внутри
PGDATA. На транзакцию приходится всего два бита:
| Биты | Статус |
|---|---|
| в процессе | ещё идёт |
| committed | зафиксирована |
| aborted | откатилась |
| sub-committed | подтранзакция (промежуточно) |
Два бита на транзакцию - это очень компактно: статусы миллионов транзакций
умещаются в мегабайты, а горячая часть кэшируется в памяти. Когда правило
видимости спрашивает «а xmin этой версии точно закоммитился?», ответ
ищется именно в clog.
Подсказки фиксации: не спрашивать дважды
Лезть в clog на каждую строку при каждом чтении было бы дорого. Поэтому
результат проверки кэшируется прямо в строке - подсказками фиксации
(hint bits) в поле t_infomask (см. tuple-header):
Флаги HEAP_XMIN_COMMITTED / HEAP_XMIN_INVALID и парные для xmax
означают «создатель/удалитель точно закоммитился / откатился». Стоит такой
флаг - и clog трогать не надо.
Подводный камень: SELECT, который пишет
Вот неожиданность. Подсказку ставит первый, кто прочитал строку после
того, как её транзакция завершилась. Установка подсказки меняет байты
страницы, а значит, помечает её грязной - и страница будет записана на
диск. Получается, что безобидный с виду SELECT сразу после массовой
загрузки данных вызывает шквал записи: он расставляет подсказки по всем
свежим строкам.
SELECT t_infomask::bit(16)
FROM heap_page_items(get_raw_page('mv', 0));-- первый SELECT после коммита расставит hint bits;
-- повторный покажет уже выставленные флаги
Поэтому «холодное» первое чтение только что загруженной таблицы бывает заметно дороже последующих - и удивляет тех, кто считает SELECT бесплатным по записи. Это не утечка и не баг, а отложенная работа по фиксации статуса.