68.6. Компоновка страницы базы данных

В данном разделе рассматривается формат страницы, используемый в таблицах и индексах Postgres Pro.[14] Последовательности и таблицы TOAST форматируются как обычные таблицы.

В дальнейшем подразумевается, что байт содержит 8 бит. В дополнение, термин элемент относится к индивидуальному значению данных, которое хранится на странице. В таблице элемент — это строка; в индексе — элемент индекса.

Каждая таблица и индекс хранятся как массив страниц фиксированного размера (обычно 8 kB, хотя можно выбрать другой размер страницы при компиляции сервера). В таблице все страницы логически эквивалентны, поэтому конкретный элемент (строка) может храниться на любой странице. В индексах первая страница обычно резервируется как метастраница, хранящая контрольную информацию, а внутри индекса могут быть разные типы страниц, в зависимости от метода доступа индекса.

Таблица 68.2 показывает общую компоновку страницы. Каждая страница имеет пять частей.

Таблица 68.2. Общая компоновка страницы

ЭлементОписание
Данные заголовка страницыДлина — 24 байта. Содержит общую информацию о странице, включая указатели свободного пространства.
Данные идентификаторов элементовМассив идентификаторов, указывающих на фактические элементы. Каждый идентификатор представляет собой пару «смещение, длина» и занимает 4 байта.
Свободное пространствоНезанятое пространство. Новые идентификаторы элементов размещаются с начала этой области, сами новые элементы — с конца.
ЭлементыСами элементы данных как таковые.
Специальное пространствоСпецифические данные метода доступа. Для различных методов хранятся различные данные. Кроме того, сюда помещается информация о 64-битных идентификаторах транзакций.

Первые 20 байт каждой страницы образуют заголовок страницы (PageHeaderData). Его формат подробно описан в Таблице 68.3. В первом поле отслеживается самая последняя запись в WAL, связанная с этой страницей. Второе поле содержит контрольную сумму страницы, если включён режим Контрольные суммы данных. Затем идёт двухбайтовое поле, содержащее биты флагов. За ним следуют три двухбайтовых целочисленных поля (pd_lower, pd_upper и pd_special). Они содержат смещения в байтах от начала страницы до начала незанятого пространства, до конца незанятого пространства и до начала специального пространства. В следующих 2 байтах заголовка страницы, в поле pd_pagesize_version, хранится размер страницы и индикатор версии. Начиная с PostgreSQL 8.3, используется версия 4; в PostgreSQL 8.1 и 8.2 использовалась версия 3; в PostgreSQL 8.0 — версия 2; в PostgreSQL 7.3 и 7.4 — версия 1; а в предыдущих выпусках — версия 0. (Основная структура страницы и формат заголовка почти во всех этих версиях одни и те же, но структура заголовка строк в куче изменялась.) Размер страницы присутствует, в основном, только для перекрёстной проверки; возможность использовать в одной инсталляции разные размеры страниц не поддерживается.

Таблица 68.3. Данные заголовка страницы (PageHeaderData)

ПолеТипДлинаОписание
pd_lsnPageXLogRecPtr8 байтLSN: Следующий байт после последнего байта записи WAL для последнего изменения на этой странице
pd_checksumuint162 байтаКонтрольная сумма страницы
pd_flagsuint162 байтаБиты признаков
pd_lowerLocationIndex2 байтаСмещение до начала свободного пространства
pd_upperLocationIndex2 байтаСмещение до конца свободного пространства
pd_specialLocationIndex2 байтаСмещение до начала специального пространства
pd_pagesize_versionuint162 байтаИнформация о размере страницы и номере версии компоновки

За заголовком страницы следуют идентификаторы элемента (ItemIdData), каждому из которых требуется 4 байта. Идентификатор элемента содержит байтовое смещение до начала элемента, его длину в байтах и несколько битов атрибутов, которые влияют на его интерпретацию. Новые идентификаторы элементов размещаются по мере необходимости от начала свободного пространства. Количество имеющихся идентификаторов элементов можно определить через значение pd_lower, которое увеличивается при добавлении нового идентификатора. Поскольку идентификатор элемента никогда не перемещается до тех пор, пока он не освобождается, его индекс можно использовать в течение длительного периода времени, чтобы ссылаться на элемент, даже когда сам элемент перемещается по странице для уплотнения свободного пространства. Фактически каждый указатель на элемент (ItemPointer, также известный как CTID), созданный Postgres Pro, состоит из номера страницы и индекса идентификатора элемента.

Сами элементы хранятся в пространстве, выделяемом в направлении от конца к началу незанятого пространства. Точная структура меняется в зависимости от того, каким будет содержимое таблицы. Как таблицы, так и последовательности используют структуру под названием HeapTupleHeaderData, которая описывается ниже.

Последний раздел является «особым разделом», который может содержать всё, что необходимо методу доступа для хранения. Например, индексы-B-деревья хранят ссылки на страницы слева и справа, равно как и некоторые другие данные, соответствующие структуре индекса. Обычные таблицы и последовательности хранят в этом разделе структуру HeapPageSpecialData. Её формат описан в Таблице 68.4.

68.6.1. Компоновка строки таблицы

Структура HeapPageSpecialData состоит из 24 байт. Первыми в ней идут два 8-байтовых поля, содержащих базу, то есть смещение, для коротких (4-байтовых) идентификаторов транзакций на этой странице. Следующее поле подсказывает, насколько вероятна возможность получить выигрыш, произведя очистку страницы: оно отслеживает самый старый XMAX на странице, не подвергавшийся очистке. Последнее поле содержит число, обозначающее тип страниц (0x1010 — для обычных таблиц, 0x1717 — для последовательностей).

Таблица 68.4. Спецданные страницы кучи (HeapPageSpecialData)

ПолеТипДлинаОписание
pd_xid_baseTransactionId8 байтБаза для коротких 4-байтных идентификаторов транзакций на этой странице
pd_multi_baseTransactionId8 байтБаза для коротких 4-байтных идентификаторов мультитранзакций на этой странице
pd_prune_xidShortTransactionId4 байтаСамый старый неочищенный идентификатор XMAX на странице или ноль при отсутствии такового
pd_magicuint324 байтаМагическое число, обозначающее тип страницы

Все строки таблицы структурированы одним и тем же образом. Они включают заголовок фиксированного размера (занимающий 40 байтов на большинстве машин), за которым следует необязательная битовая карта пустых значений, необязательное поле идентификатора объекта и данные пользователя. Подробное описание заголовка представлено в Таблице 68.5. Собственно пользовательские данные (столбцы строки) начинаются после смещения, заданного в t_hoff, которое должно всегда быть кратным величине MAXALIGN для платформы. Битовая карта пустых значений присутствует, только если в поле t_infomask установлен бит HEAP_HASNULL. В случае присутствия она располагается сразу за фиксированным заголовком и занимает столько байт, сколько необходимо для представления отдельного бита для каждого столбца данных (то есть, всего t_natts битов). В этом списке битов 1 соответствует значению, отличному от NULL, а 0 — NULL. Когда эта битовая карта отсутствует, считается, что все столбцы отличны от NULL. Идентификатор объекта присутствует, только если в t_infomask установлен бит HEAP_HASOID. Если он присутствует, он располагается сразу перед границей выравнивания t_hoff. Любое дополнительное выравнивание, необходимое, чтобы t_hoff было кратным MAXALIGN, будет располагаться между битовой картой пустых значений и идентификатором объекта. (Это в свою очередь гарантирует, что идентификатор объекта будет выровнен правильно.)

Таблица 68.5. Данные заголовка строки таблицы (HeapTupleHeaderData)

ПолеТипДлинаОписание
t_xminShortTransactionId4 байтазначение XID вставки
t_xmaxShortTransactionId4 байтазначение XID удаления
t_cidCommandId4 байтазначение CID для вставки и/или удаления (пересекается с t_xvac)
t_xvacShortTransactionId4 байтаXID для операции VACUUM, которая перемещает версию строки
t_ctidItemPointerData6 байттекущее значение TID этой или более новой версии строки
t_infomask2uint162 байтаколичество атрибутов плюс различные биты флагов
t_infomaskuint162 байтаразличные биты флагов
t_hoffuint81 байтотступ до пользовательских данных

Интерпретировать текущие данные можно только с использованием информации, полученной из других таблиц, в основном из pg_attribute. Ключевыми значениями, необходимыми для определения расположения полей, являются attlen и attalign. Не существует способа непосредственного получения заданного атрибута, кроме случая, когда имеются только поля фиксированной длины и при этом нет значений NULL. Все эти особенности учитываются в функциях heap_getattr, fastgetattr и heap_getsysattr.

Чтобы прочитать данные, необходимо просмотреть каждый атрибут по очереди. В первую очередь нужно проверить, является ли значение поля пустым согласно битовой карте пустых значений. Если это так, можно переходить к следующему полю. Затем следует убедиться, что выравнивание является верным. Если это поле фиксированной ширины, берутся просто все его байты. Если это поле переменной длины (attlen = -1), всё несколько сложнее. Все типы данных с переменной длиной имеют общую структуру заголовка struct varlena, которая включает общую длину сохранённого значения и некоторые биты флагов. В зависимости от установленных флагов, данные могут храниться либо локально, либо в таблице TOAST. Также, возможно сжатие данных (см. Раздел 68.2).



[14] Фактически индексные методы доступа не нуждаются в этом формате страниц. Все существующие индексные методы в действительности используют этот основной формат, но данные, хранящиеся в индексных метастраницах обычно не следуют правилам компоновки.