63.6. Компоновка страницы базы данных
В данном разделе рассматривается формат страницы, используемый в таблицах и индексах Postgres Pro.[1] Последовательности и таблицы TOAST форматируются как обычные таблицы.
В дальнейшем подразумевается, что байт содержит 8 бит. В дополнение, термин элемент относится к индивидуальному значению данных, которое хранится на странице. В таблице элемент — это строка; в индексе — элемент индекса.
Каждая таблица и индекс хранятся как массив страниц фиксированного размера (обычно 8 kB, хотя можно выбрать другой размер страницы при компиляции сервера). В таблице все страницы логически эквивалентны, поэтому конкретный элемент (строка) может храниться на любой странице. В индексах первая страница обычно резервируется как метастраница, хранящая контрольную информацию, а внутри индекса могут быть разные типы страниц, в зависимости от метода доступа индекса.
Таблица 63.2 показывает общую компоновку страницы. Каждая страница имеет пять частей.
Таблица 63.2. Общая компоновка страницы
Элемент | Описание |
---|---|
Данные заголовка страницы | Длина — 24 байта. Содержит общую информацию о странице, включая указатели свободного пространства. |
Данные идентификаторов элементов | Массив идентификаторов, указывающих на фактические элементы. Каждый идентификатор представляет собой пару «смещение, длина» и занимает 4 байта. |
Свободное пространство | Незанятое пространство. Новые идентификаторы элементов размещаются с начала этой области, сами новые элементы — с конца. |
Элементы | Сами элементы данных как таковые. |
Специальное пространство | Специфические данные метода доступа. Для различных методов хранятся различные данные. Кроме того, сюда помещается информация о 64-битных идентификаторах транзакций. |
Первые 40 байт каждой страницы образуют заголовок страницы (PageHeaderData
). Его формат подробно описан в Таблице 63.3. В первом поле отслеживается самая последняя запись в WAL, связанная с этой страницей. Второе поле содержит контрольную сумму страницы, если включён режим Контрольные суммы данных. Затем идёт двухбайтовое поле, содержащее биты флагов. За ним следуют три двухбайтовых целочисленных поля (pd_lower
, pd_upper
и pd_special
). Они содержат смещения в байтах от начала страницы до начала незанятого пространства, до конца незанятого пространства и до начала специального пространства. Затем идут два 8-байтовых поля, содержащих эпоху, то есть смещение, для коротких (4-байтовых) идентификаторов транзакций на этой странице. В следующих 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 на странице, не подвергавшийся очистке.
Таблица 63.3. Данные заголовка страницы (PageHeaderData)
Поле | Тип | Длина | Описание |
---|---|---|---|
pd_lsn | PageXLogRecPtr | 8 байт | LSN: Следующий байт после последнего байта записи xlog для последнего изменения на этой странице |
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_xid_epoch | TransactionId | 8 байт | Эпоха для коротких 4-байтных идентификаторов на этой странице |
pd_multi_epoch | TransactionId | 8 байт | Эпоха для коротких 4-байтных мультитранзакций на этой странице |
pd_prune_xid | ShortTransactionId | 4 байта | Самый старый неочищенный идентификатор XMAX на странице или ноль при отсутствии такового |
Всю подробную информацию можно найти в src/include/storage/bufpage.h
.
За заголовком страницы следуют идентификаторы элемента (ItemIdData
), каждому из которых требуется 4 байта. Идентификатор элемента содержит байтовое смещение до начала элемента, его длину в байтах и несколько битов атрибутов, которые влияют на его интерпретацию. Новые идентификаторы элементов размещаются по мере необходимости от начала свободного пространства. Количество имеющихся идентификаторов элементов можно определить через значение pd_lower
, которое увеличивается при добавлении нового идентификатора. Поскольку идентификатор элемента никогда не перемещается до тех пор, пока он не освобождается, его индекс можно использовать в течение длительного периода времени, чтобы ссылаться на элемент, даже когда сам элемент перемещается по странице для уплотнения свободного пространства. Фактически каждый указатель на элемент (ItemPointer
, также известный как CTID
), созданный Postgres Pro, состоит из номера страницы и индекса идентификатора элемента.
Сами элементы хранятся в пространстве, выделяемом в направлении от конца к началу незанятого пространства. Точная структура меняется в зависимости от того, каким будет содержимое таблицы. Как таблицы, так и последовательности используют структуру под названием HeapTupleHeaderData
, которая описывается ниже.
Последний раздел является «особым разделом», который может содержать всё, что необходимо методу доступа для хранения. Например, индексы-B-деревья хранят ссылки на страницы слева и справа, равно как и некоторые другие данные, соответствующие структуре индекса. Обычные таблицы не используют особый раздел вовсе (что указывается установкой значения pd_special
равным размеру страницы).
Все строки таблицы структурированы одним и тем же образом. Они включают заголовок фиксированного размера (занимающий 40 байтов на большинстве машин), за которым следует необязательная битовая карта пустых значений, необязательное поле идентификатора объекта и данные пользователя. Подробное описание заголовка представлено в Таблице 63.4. Собственно пользовательские данные (столбцы строки) начинаются после смещения, заданного в t_hoff
, которое должно всегда быть кратным величине MAXALIGN для платформы. Битовая карта пустых значений присутствует, только если в поле t_infomask
установлен бит HEAP_HASNULL. В случае присутствия она располагается сразу за фиксированным заголовком и занимает столько байт, сколько необходимо для представления отдельного бита для каждого столбца данных (то есть, всего t_natts
битов). В этом списке битов 1 соответствует значению, отличному от NULL, а 0 — NULL. Когда эта битовая карта отсутствует, считается, что все столбцы отличны от NULL. Идентификатор объекта присутствует, только если в t_infomask
установлен бит HEAP_HASOID. Если он присутствует, он располагается сразу перед границей выравнивания t_hoff
. Любое дополнительное выравнивание, необходимое, чтобы t_hoff
было кратным MAXALIGN, будет располагаться между битовой картой пустых значений и идентификатором объекта. (Это в свою очередь гарантирует, что идентификатор объекта будет выровнен правильно.)
Таблица 63.4. Данные заголовка строки таблицы (HeapTupleHeaderData)
Поле | Тип | Длина | Описание |
---|---|---|---|
t_xmin | ShortTransactionId | 4 байта | значение XID вставки |
t_xmax | ShortTransactionId | 4 байта | значение XID удаления |
t_cid | CommandId | 4 байта | значение CID для вставки и/или удаления (пересекается с t_xvac) |
t_xvac | ShortTransactionId | 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. Также, возможно сжатие данных (см. Раздел 63.2).
[1] Фактически индексные методы доступа не нуждаются в этом формате страниц. Все существующие индексные методы в действительности используют этот основной формат, но данные, хранящиеся в индексных метастраницах обычно не следуют правилам компоновки.