69.6. Компоновка страницы базы данных #
В данном разделе рассматривается формат страницы, используемый в таблицах и индексах Postgres Pro.[16] Последовательности и таблицы TOAST форматируются как обычные таблицы.
В дальнейшем подразумевается, что байт содержит 8 бит. В дополнение, термин элемент относится к индивидуальному значению данных, которое хранится на странице. В таблице элемент — это строка; в индексе — элемент индекса.
Каждая таблица и индекс хранятся как массив страниц фиксированного размера (обычно 8 kB, хотя можно выбрать другой размер страницы при компиляции сервера). В таблице все страницы логически эквивалентны, поэтому конкретный элемент (строка) может храниться на любой странице. В индексах первая страница обычно резервируется как метастраница, хранящая контрольную информацию, а внутри индекса могут быть разные типы страниц, в зависимости от метода доступа индекса.
Таблица 69.2 показывает общую компоновку страницы. Каждая страница имеет пять частей.
Таблица 69.2. Общая компоновка страницы
Элемент | Описание |
---|---|
Данные заголовка страницы | Длина — 24 байта. Содержит общую информацию о странице, включая указатели свободного пространства. |
Данные идентификаторов элементов | Массив идентификаторов, указывающих на фактические элементы. Каждый идентификатор представляет собой пару «смещение, длина» и занимает 4 байта. |
Свободное пространство | Незанятое пространство. Новые идентификаторы элементов размещаются с начала этой области, сами новые элементы — с конца. |
Элементы | Сами элементы данных как таковые. |
Специальное пространство | Специфические данные метода доступа. Для различных методов хранятся различные данные. Для обычных таблиц таких данных нет. |
Первые 24 байта каждой страницы образуют заголовок страницы (PageHeaderData
). Его формат подробно описан в Таблице 69.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. (Основная структура страницы и формат заголовка почти во всех этих версиях одни и те же, но структура заголовка строк в куче изменялась.) Размер страницы присутствует в основном только для перекрёстной проверки; возможность использовать в одной инсталляции разные размеры страниц не поддерживается. Последнее поле подсказывает, насколько вероятна возможность получить выигрыш, произведя очистку страницы: оно отслеживает самый старый XMAX на странице, не подвергавшийся очистке.
Таблица 69.3. Данные заголовка страницы (PageHeaderData)
Поле | Тип | Длина | Описание |
---|---|---|---|
pd_lsn | PageXLogRecPtr | 8 байт | LSN: Следующий байт после последнего байта записи WAL для последнего изменения на этой странице |
pd_checksum | uint16 | 2 байта | Контрольная сумма страницы |
pd_flags | uint16 | 2 байта | Биты признаков |
pd_lower | LocationIndex | 2 байта | Смещение до начала свободного пространства |
pd_upper | LocationIndex | 2 байта | Смещение до конца свободного пространства |
pd_special | LocationIndex | 2 байта | Смещение до начала специального пространства |
pd_pagesize_version | uint16 | 2 байта | Информация о размере страницы и номере версии компоновки |
pd_prune_xid | TransactionId | 4 байта | Самый старый неочищенный идентификатор XMAX на странице или ноль при отсутствии такового |
За заголовком страницы следуют идентификаторы элемента (ItemIdData
), каждому из которых требуется 4 байта. Идентификатор элемента содержит байтовое смещение до начала элемента, его длину в байтах и несколько битов атрибутов, которые влияют на его интерпретацию. Новые идентификаторы элементов размещаются по мере необходимости от начала свободного пространства. Количество имеющихся идентификаторов элементов можно определить через значение pd_lower
, которое увеличивается при добавлении нового идентификатора. Поскольку идентификатор элемента никогда не перемещается до тех пор, пока он не освобождается, его индекс можно использовать в течение длительного периода времени, чтобы ссылаться на элемент, даже когда сам элемент перемещается по странице для уплотнения свободного пространства. Фактически каждый указатель на элемент (ItemPointer
, также известный как CTID
), созданный Postgres Pro, состоит из номера страницы и индекса идентификатора элемента.
Сами элементы хранятся в пространстве, выделяемом в направлении от конца к началу незанятого пространства. Точная структура меняется в зависимости от того, каким будет содержимое таблицы. Как таблицы, так и последовательности используют структуру под названием HeapTupleHeaderData
, которая описывается ниже.
Последний раздел является «особым разделом», который может содержать всё, что необходимо методу доступа для хранения. Например, индексы-B-деревья хранят ссылки на страницы слева и справа, равно как и некоторые другие данные, соответствующие структуре индекса. Обычные таблицы не используют особый раздел вовсе (что указывается установкой значения pd_special
равным размеру страницы).
Рисунок 69.1 показывает, как эти компоненты размещаются в странице.
Рисунок 69.1. Компоновка страницы
69.6.1. Компоновка строки таблицы #
Все строки таблицы имеют одинаковую структуру. Они включают заголовок фиксированного размера (занимающий 23 байта на большинстве машин), за которым следует необязательная битовая карта пустых значений, необязательное поле идентификатора объекта и данные пользователя. Подробное описание заголовка представлено в Таблице 69.4. Актуальные пользовательские данные (столбцы строки) начинаются после смещения, заданного в t_hoff
, которое должно всегда быть кратным величине MAXALIGN для платформы. Битовая карта пустых значений имеется тогда, когда бит HEAP_HASNULL установлен в значении t_infomask
. В случае наличия она начинается сразу после фиксированного заголовка и занимает столько байтов, сколько требуется для размещения битов по количеству столбцов (т. е. число битов равно количеству атрибутов, определяемому полем t_infomask2
). В этом списке битов установленный бит означает непустое значение, а сброшенный соответствует пустому значению. Когда битовая карта отсутствует, все столбцы считаются непустыми. Идентификатор объекта присутствует, если только в значении t_infomask
установлен бит HEAP_HASOID_OLD. Если он есть, он расположен сразу перед началом t_hoff
. Любое заполнение, необходимое для того, чтобы сделать t_hoff
кратным MAXALIGN, будет добавлено между битовой картой пустых значений и идентификатором объекта. (Это в свою очередь гарантирует, что идентификатор объекта будет правильно выровнен.)
Таблица 69.4. Данные заголовка строки таблицы (HeapTupleHeaderData)
Поле | Тип | Длина | Описание |
---|---|---|---|
t_xmin | TransactionId | 4 байта | значение XID вставки |
t_xmax | TransactionId | 4 байта | значение XID удаления |
t_cid | CommandId | 4 байта | значение CID для вставки и/или удаления (пересекается с t_xvac) |
t_xvac | TransactionId | 4 байта | XID для операции VACUUM, которая перемещает версию строки |
t_ctid | ItemPointerData | 6 байт | текущее значение TID этой или более новой версии строки |
t_infomask2 | uint16 | 2 байта | количество атрибутов плюс различные биты флагов |
t_infomask | uint16 | 2 байта | различные биты флагов |
t_hoff | uint8 | 1 байт | отступ до пользовательских данных |
Интерпретировать текущие данные можно только с использованием информации, полученной из других таблиц, в основном из pg_attribute
. Ключевыми значениями, необходимыми для определения расположения полей, являются attlen
и attalign
. Не существует способа непосредственного получения заданного атрибута, кроме случая, когда имеются только поля фиксированной длины и при этом нет значений NULL. Все эти особенности учитываются в функциях heap_getattr, fastgetattr и heap_getsysattr.
Чтобы прочитать данные, необходимо просмотреть каждый атрибут по очереди. В первую очередь нужно проверить, является ли значение поля пустым согласно битовой карте пустых значений. Если это так, можно переходить к следующему полю. Затем следует убедиться, что выравнивание является верным. Если это поле фиксированной ширины, берутся просто все его байты. Если это поле переменной длины (attlen = -1), всё несколько сложнее. Все типы данных с переменной длиной имеют общую структуру заголовка struct varlena
, которая включает общую длину сохранённого значения и некоторые биты флагов. В зависимости от установленных флагов, данные могут храниться либо локально, либо в таблице TOAST. Также, возможно сжатие данных (см. Раздел 69.2).
[16] На самом деле этот формат страниц не является обязательным ни для табличных, ни для индексных методов доступа. Его всегда использует табличный метод heap
и все существующие индексные методы, но в метастраницах индексов данные обычно компонуются по другим правилам.