69.6. Компоновка страницы базы данных
В данном разделе рассматривается формат страницы, используемый в таблицах и индексах PostgreSQL.[15] Последовательности и таблицы 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 на странице или ноль при отсутствии такового |
Всю подробную информацию можно найти в src/include/storage/bufpage.h
.
За заголовком страницы следуют идентификаторы элемента (ItemIdData
), каждому из которых требуется 4 байта. Идентификатор элемента содержит байтовое смещение до начала элемента, его длину в байтах и несколько битов атрибутов, которые влияют на его интерпретацию. Новые идентификаторы элементов размещаются по мере необходимости от начала свободного пространства. Количество имеющихся идентификаторов элементов можно определить через значение pd_lower
, которое увеличивается при добавлении нового идентификатора. Поскольку идентификатор элемента никогда не перемещается до тех пор, пока он не освобождается, его индекс можно использовать в течение длительного периода времени, чтобы ссылаться на элемент, даже когда сам элемент перемещается по странице для уплотнения свободного пространства. Фактически каждый указатель на элемент (ItemPointer
, также известный как CTID
), созданный PostgreSQL, состоит из номера страницы и индекса идентификатора элемента.
Сами элементы хранятся в пространстве, выделяемом в направлении от конца к началу незанятого пространства. Точная структура меняется в зависимости от того, каким будет содержимое таблицы. Как таблицы, так и последовательности используют структуру под названием 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 байт | отступ до пользовательских данных |
Всю подробную информацию можно найти в src/include/access/htup_details.h
.
Интерпретировать текущие данные можно только с использованием информации, полученной из других таблиц, в основном из pg_attribute
. Ключевыми значениями, необходимыми для определения расположения полей, являются attlen
и attalign
. Не существует способа непосредственного получения заданного атрибута, кроме случая, когда имеются только поля фиксированной длины и при этом нет значений NULL. Все эти особенности учитываются в функциях heap_getattr, fastgetattr и heap_getsysattr.
Чтобы прочитать данные, необходимо просмотреть каждый атрибут по очереди. В первую очередь нужно проверить, является ли значение поля пустым согласно битовой карте пустых значений. Если это так, можно переходить к следующему полю. Затем следует убедиться, что выравнивание является верным. Если это поле фиксированной ширины, берутся просто все его байты. Если это поле переменной длины (attlen = -1), всё несколько сложнее. Все типы данных с переменной длиной имеют общую структуру заголовка struct varlena
, которая включает общую длину сохранённого значения и некоторые биты флагов. В зависимости от установленных флагов, данные могут храниться либо локально, либо в таблице TOAST. Также, возможно сжатие данных (см. Раздел 69.2).
[15] На самом деле этот формат страниц не является обязательным ни для табличных, ни для индексных методов доступа. Его всегда использует табличный метод heap
и все существующие индексные методы, но в метастраницах индексов данные обычно компонуются по другим правилам.