# Заголовок кортежа _Хранение и формат на диске · PostgreSQL Knowledge Base_ **TL;DR:** Перед данными каждой строки лежит служебный заголовок в 23 байта, выровненный до 24. В нём t_xmin и t_xmax (кто вставил и кто удалил версию), t_ctid (ссылка на себя или на новую версию), t_infomask с флагами и t_hoff - смещение до пользовательских данных. Это поля, на которых стоит весь MVCC. У каждой строки в heap есть **заголовок кортежа** - фиксированный блок служебных полей перед самими данными. Он занимает 23 байта и выравнивается до 24. В этих байтах закодировано, какая транзакция строку создала, какая удалила, где её следующая версия и как читать данные дальше. Прочитать заголовки всех строк на странице: ```sql 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](/courses/postgres/kb/xmin-xmax.md). `t_ctid` обычно указывает на саму строку. Но после `UPDATE` старая версия начинает указывать на новую: так получается цепочка версий. Поэтому обновление одной строки можно проследить, идя по `ctid` от версии к версии. ```sql 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](/courses/postgres/kb/clog-hint-bits.md). Другие биты говорят, есть ли в строке NULL-значения и значения переменной длины - это влияет на размер заголовка через `t_hoff`. ## t_hoff и выравнивание `t_hoff` - это смещение от начала кортежа до первого пользовательского байта. Без NULL-значений заголовок занимает ровно 24 байта, и `t_hoff = 24`. Если в строке есть NULL, после заголовка добавляется битовая карта NULL, и `t_hoff` вырастает. Где именно лягут сами колонки за заголовком, определяет их выравнивание - об этом [column-alignment](/courses/postgres/kb/column-alignment.md). ## Команды ```sql SELECT lp, t_xmin, t_xmax, t_ctid, t_hoff FROM heap_page_items(get_raw_page('t', 0)); ``` Версионные поля и смещение данных для всех строк страницы 0 ```sql SELECT ctid, xmin, xmax, * FROM t; ``` Системные колонки xmin/xmax/ctid прямо в выдаче таблицы ```sql SELECT t_infomask::bit(16) FROM heap_page_items(get_raw_page('t', 0)); ``` infomask в двоичном виде - видно выставленные флаги и hint bits ## См. также - [xmin, xmax и правила видимости](/courses/postgres/kb/xmin-xmax.md) - [Line pointers и lp_flags](/courses/postgres/kb/line-pointers.md) - [Выравнивание и порядок колонок](/courses/postgres/kb/column-alignment.md) - [clog и подсказки фиксации (hint bits)](/courses/postgres/kb/clog-hint-bits.md)