# Line pointers и lp_flags _Хранение и формат на диске · PostgreSQL Knowledge Base_ **TL;DR:** Указатель строки (line pointer) - это 4 байта в начале страницы: смещение до кортежа, его длина и флаг состояния. Флаг lp_flags бывает четырёх видов: unused, normal, redirect, dead. Внешний адрес строки (ctid) - это номер указателя, а не позиция в байтах, поэтому строка может двигаться внутри страницы, не меняя адреса. Сразу за заголовком страницы идёт массив **указателей строк** (line pointers). Каждый указатель - всего 4 байта и описывает одну строку: где она лежит (смещение), сколько занимает (длина) и в каком она состоянии (флаг). Раскладку страницы целиком разбирает [page-layout](/courses/postgres/kb/page-layout.md). Указатель нужен, чтобы у строки был **стабильный адрес**. Адрес строки внутри таблицы - это пара «номер страницы, номер указателя», она же `ctid`. Когда страница уплотняется и кортеж физически переезжает, меняется только смещение внутри указателя. Номер указателя, а значит и `ctid`, остаётся прежним. Без этого слоя любое движение строки ломало бы ссылки на неё. ## Четыре состояния указателя Флаг `lp_flags` (2 бита) говорит, что за указателем стоит: | lp_flags | Имя | Смысл | |---|---|---| | 0 | LP_UNUSED | слот свободен, можно переиспользовать | | 1 | LP_NORMAL | ведёт на нормальный кортеж | | 2 | LP_REDIRECT | ведёт на другой указатель (звено HOT-цепочки) | | 3 | LP_DEAD | строка мертва, место под кортеж уже освобождено | Посмотреть флаги всех строк на странице: ```sql SELECT lp, lp_off, lp_len, lp_flags FROM heap_page_items(get_raw_page('flights', 0)); -- lp | lp_off | lp_len | lp_flags -- ----+--------+--------+---------- -- 1 | 8144 | 48 | 1 -- 2 | 8096 | 48 | 1 ``` `lp_flags = 1` (LP_NORMAL) - обычная живая строка. `lp_off` - смещение кортежа от начала страницы, `lp_len` - его длина в байтах. Указатели лежат по возрастанию (1, 2, 3 ...), а смещения `lp_off` убывают: кортежи заполняют страницу с конца. ## Redirect и dead - следы очистки Два «странных» состояния появляются после обновлений и очистки. **LP_REDIRECT** возникает при внутристраничной очистке HOT-цепочки. Когда старая версия строки удаляется, но на её указатель ещё ссылаются, указатель превращают в перенаправление: он не хранит кортеж, а отправляет к началу живой цепочки версий. `lp_len` у такого указателя равен 0. **LP_DEAD** помечает указатель, чья строка уже не видна ни одной транзакции. Место под сам кортеж может быть уже освобождено, а указатель ждёт, когда его переведут в UNUSED. Это промежуточное состояние между «живой» и «полностью свободный». ## Зачем это знать Когда в `heap_page_items` встречаются нули в `lp_len` или флаги 2 и 3 - это не мусор, а рабочая механика. Redirect-указатели объясняют, почему обновление строки иногда не трогает индекс. Dead-указатели показывают, что вакуум уже прошёлся по странице. Без этого слоя не понять ни HOT, ни то, как `ctid` остаётся валидным, пока строка ездит по странице. ## Команды ```sql SELECT lp, lp_off, lp_len, lp_flags FROM heap_page_items(get_raw_page('flights', 0)); ``` Все указатели строк на странице 0: смещение, длина, флаг состояния ```sql SELECT ctid, * FROM flights LIMIT 3; ``` Системная колонка ctid - пара (страница, номер указателя) ```sql SELECT count(*) FILTER (WHERE lp_flags = 2) AS redirects FROM heap_page_items(get_raw_page('t', 0)); ``` Сколько на странице redirect-указателей (следов HOT-очистки) ## См. также - [Раскладка страницы 8 КБ](/courses/postgres/kb/page-layout.md) - [Заголовок кортежа](/courses/postgres/kb/tuple-header.md) - [Visibility Map (карта видимости)](/courses/postgres/kb/visibility-map.md)