У каждой строки в heap есть заголовок кортежа - фиксированный блок служебных полей перед самими данными. Он занимает 23 байта и выравнивается до 24. В этих байтах закодировано, какая транзакция строку создала, какая удалила, где её следующая версия и как читать данные дальше.
Прочитать заголовки всех строк на странице:
SELECT lp, t_xmin, t_xmax, t_ctid, t_infomask2, t_infomask, t_hoff
FROM heap_page_items(get_raw_page('t', 0));Поля заголовка
| Поле | Размер | Смысл |
|---|---|---|
t_xmin | 4 байта | xid транзакции, вставившей эту версию |
t_xmax | 4 байта | xid транзакции, удалившей версию (0, если жива) |
t_field3 | 4 байта | command id или служебное поле вакуума |
t_ctid | 6 байт | адрес этой или следующей версии: (страница, указатель) |
t_infomask2 | 2 байта | число колонок + флаги HOT |
t_infomask | 2 байта | битовые флаги состояния и подсказки фиксации |
t_hoff | 1 байт | смещение до пользовательских данных |
xmin, xmax и ctid - сердце версионности
t_xmin и t_xmax определяют время жизни версии строки. Версия видна
транзакции, если её xmin уже зафиксирован и попадает в снимок, а xmax
ещё нет. Правила видимости подробно разбирает xmin-xmax.
t_ctid обычно указывает на саму строку. Но после UPDATE старая версия
начинает указывать на новую: так получается цепочка версий. Поэтому
обновление одной строки можно проследить, идя по ctid от версии к
версии.
INSERT INTO t VALUES (1, 'a'); -- появилась версия с ctid (0,1)
UPDATE t SET note = 'b' WHERE id = 1; -- старая версия (0,1) теперь ведёт на (0,2)
infomask: флаги и подсказки
t_infomask хранит битовые флаги. Часть из них - подсказки фиксации
(hint bits): отметки «xmin точно зафиксирован» и «xmax точно зафиксирован».
Их ставит первый прочитавший строку, чтобы следующим не лезть в clog за
статусом транзакции. Механику разбирает clog-hint-bits. Другие биты
говорят, есть ли в строке NULL-значения и значения переменной длины - это
влияет на размер заголовка через t_hoff.
t_hoff и выравнивание
t_hoff - это смещение от начала кортежа до первого пользовательского
байта. Без NULL-значений заголовок занимает ровно 24 байта, и t_hoff = 24.
Если в строке есть NULL, после заголовка добавляется битовая карта NULL,
и t_hoff вырастает. Где именно лягут сами колонки за заголовком,
определяет их выравнивание - об этом column-alignment.