Макет страницы базы данных#

Примечание

Эта страница переведена нейросетью GigaChat.

Этот раздел предоставляет обзор формата страниц, используемого в таблицах и индексах PostgreSQL. Последовательности и таблицы TOAST отформатированы так же, как обычная таблица.

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

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

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

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

Элемент

Описание

Заголовок страницы

Длина 24 байта. Содержит общую информацию о странице, включая указатели свободного пространства.

Данные элемента

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

Свободное пространство

Не распределенное пространство. Новые идентификаторы элементов распределяются с начала этой области, новые элементы - с конца.

Элементы

Фактические элементы сами по себе.

Специальное пространство

Метод доступа к индексу специфичен для конкретных данных. Разные методы хранят разные данные. Пусто в обычных таблицах.

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

Макет PageHeaderData:

Поле

Тип

Длина

Описание

pd_lsn

PageXLogRecPtr

8 байт

LSN: следующий байт после последнего байта записи WAL для последнего изменения этой страницы

pd_checksum

uint16

2 байта

Контрольная сумма страницы

pd_flags

uint16

2 байта

Флаги битов

pd_lower

Индекс местоположения

2 байта

Смещение начала свободного пространства

pd_upper

Индекс местоположения

2 байта

Смещение до конца свободного пространства

pd_специальный

Индекс местоположения

2 байта

Смещение начала специального пространства

pd_размер_страницы_версия

uint16

2 байта

Информация о версии размера и макета страницы

pd_prune_xid

Идентификатор транзакции

4 байта

Самый старый непроходимый XMAX на странице или ноль, если его нет

Все подробности можно найти в src/include/storage/bufpage.h.

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

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

Последний раздел - это «специальный раздел», который может содержать все, что метод доступа хочет сохранить. Например, индексы b-дерева хранят ссылки на левую и правую соседние страницы, а также некоторые другие данные, относящиеся к структуре индекса. Обычные таблицы вообще не используют специальный раздел (это указывается установкой pd_special равной размеру страницы).

На рисунке 73.1 показано, как эти части расположены на странице.

Рисунок 73.1. Макет страницы

Макет строки таблицы#

Все строки таблиц структурированы одинаково. Существует заголовок фиксированного размера (занимающий 23 байта на большинстве машин), за которым следует необязательная карта нулей, необязательное поле идентификатора объекта и пользовательские данные. Фактические пользовательские данные (столбцы строки) начинаются с смещения, указанного в t_hoff, которое всегда должно быть кратным расстоянию MAXALIGN для платформы. Карта нулей присутствует только в том случае, если бит HEAP_HASNULL установлен в t_infomask. Если она присутствует, то начинается сразу после фиксированного заголовка и занимает достаточно байтов, чтобы иметь один бит на каждый столбец данных (то есть количество битов, равное количеству атрибутов в t_infomask2). В этом списке битов бит 1 указывает не-нулевое значение, а бит 0 - нулевое. Когда карта нулей отсутствует, предполагается, что все столбцы не являются нулевыми. Идентификатор объекта присутствует только в том случае, если бит HEAP_HASOID_OLD установлен в t_infomask. Если он присутствует, он появляется прямо перед границей t_hoff. Любая необходимая подгонка для того, чтобы сделать t_hoff кратной MAXALIGN, появится между картой нулей и идентификатором объекта. (Это, в свою очередь, гарантирует, что идентификатор объекта правильно выровнен.)

Макет HeapTupleHeaderData:

Поле

Тип

Длина

Описание

t_xmin

Идентификатор транзакции

4 байта

вставить штамп XID

t_xmax

Идентификатор транзакции

4 байта

удалить штамп XID

t_cid

Идентификатор команды

4 байта

вставка и/или удаление штампа CID (перекрывается с t_xvac)

t_xvac

Идентификатор транзакции

4 байта

XID для операции VACUUM перемещения версии строки

t_ctid

ItemPointerData

6 байт

текущий TID этой или более новой версии строки

t_infomask2

uint16

2 байта

количество атрибутов плюс различные флаги

t_infomask

uint16

2 байта

различные флаги бит

t_hoff

uint8

1 байт

смещение до пользовательских данных

Все подробности можно найти в src/include/access/htup_details.h.

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

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