68.6. 数据库页面布局

本节概述了 PostgreSQL 表和索引中使用的页面格式。 [14]序列和 TOAST 表的格式类似于常规表。

在下面的说明中,假定* byte 包含 8 位。另外,术语项目*是指存储在页面上的单个数据值。在表中,一项是一行;在索引中,项目是索引条目。

每个表和索引都存储为固定大小的* page 数组(通常为 8 kB,尽管在编译服务器时可以选择不同的页面大小)。在一个表中,所有页面在逻辑上都是等效的,因此特定项目(行)可以存储在任何页面中。在索引中,通常将第一页保留为 metapage *来保存控制信息,并且根据索引访问方法,索引内可以有不同类型的页面。

Table 68.2显示页面的总体布局。每页分为五个部分。

表 68.2. 整体页面布局

ItemDescription
PageHeaderData24 个字节长。包含有关页面的常规信息,包括可用空间指针。
ItemIdData指向实际项目的项目标识符数组。每个条目都是一个(偏移,长度)对。每个项目 4 个字节。
Free space未分配的空间。从该区域的开始分配新的项目标识符,从结尾开始分配新的项目。
Items实际项目本身。
Special space索引访问方法特定的数据。不同的方法存储不同的数据。在普通表中为空。

每页的前 24 个字节由页眉(PageHeaderData)组成。其格式在Table 68.3中详细说明。第一个字段跟踪与此页面相关的最新 WAL 条目。如果启用了data checksums,则第二个字段包含页面校验和。接下来是一个包含标志位的 2 字节字段。其后是三个 2 字节整数字段(pd_lowerpd_upperpd_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.(在大多数这些版本中,基本页面布局和 Headers 格式没有更改,但堆行 Headers 的布局已更改.)页面大小基本上仅作为交叉检查出现;不支持安装中的页面大小超过一个。最后一个字段是一个提示,显示修剪页面是否可能有利可图:它跟踪页面上最早的未修剪 XMAX。

表 68.3. PageHeaderData 布局

FieldTypeLengthDescription
pd_lsnPageXLogRecPtr8 bytesLSN:WAL 记录的最后一个字节之后的下一个字节,用于此页面的最后更改
pd_checksumuint162 bytesPage checksum
pd_flagsuint162 bytesFlag bits
pd_lowerLocationIndex2 bytes偏移到可用空间的开始
pd_upperLocationIndex2 bytes偏移到可用空间的尽头
pd_specialLocationIndex2 bytes偏移到特殊空间的开始
pd_pagesize_versionuint162 bytes页面大小和布局版本号信息
pd_prune_xidTransactionId4 bytes页面上最旧的未修剪 XMAX,如果没有,则为零

所有详细信息都可以在src/include/storage/bufpage.h中找到。

页面标题后面是项目标识符(ItemIdData),每个标识符都需要四个字节。项标识符包含项开头的字节偏移量,项的长度(以字节为单位)和一些影响项解释的属性位。从未分配空间的开头开始,根据需要分配新的项目标识符。可以通过查看pd_lower来确定存在的项目标识符的数量,将其增加以分配新的标识符。因为项目标识符直到被释放才被移动,所以即使项目本身在页面上四处移动以压缩可用空间,它的索引也可以长期用于引用项目。实际上,由 PostgreSQL 创建的指向项目(ItemPointer,也称为CTID)的每个指针都由页码和项目标识符的索引组成。

项目本身存储在从未分配空间的末尾向后分配的空间中。确切的结构取决于表所包含的内容。表和序列都使用名为HeapTupleHeaderData的结构,如下所述。

最后一部分是“特殊部分”,其中可以包含访问方法希望存储的任何内容。例如,b 树索引存储到页面的左右同级的链接,以及与索引结构相关的其他一些数据。普通表根本不使用特殊节(通过将pd_special设置为等于页面大小来表示)。

68 .6.1. 表格行布局

所有表行的结构均相同。有一个固定大小的 Headers(在大多数计算机上占 23 个字节),后跟一个可选的空位图,一个可选的对象 ID 字段以及用户数据。标题在Table 68.4中详细说明。实际用户数据(行的列)从t_hoff指示的偏移量开始,该偏移量必须始终是平台的 MAXALIGN 距离的倍数。只有在t_infomask中设置了* HEAP_HASNULL 位时,才出现空位图。如果存在,它将在固定头之后立即开始,并且占用足够的字节以使每个数据列具有一位(即,总共t_natts位)。在此位列表中,1 位表示非空,0 位表示空。当位图不存在时,所有列均假定为非空。仅当在t_infomask中设置 HEAP_HASOID *时,才存在对象 ID。如果存在,它将出现在t_hoff边界之前。在空位图和对象 ID 之间将出现使t_hoff为 MAXALIGN 倍数所需的任何填充。 (这反过来又确保了对象 ID 正确对齐.)

表 68.4. HeapTupleHeaderData 布局

FieldTypeLengthDescription
t_xminTransactionId4 bytes插入 XID 戳
t_xmaxTransactionId4 bytes删除 XID 图章
t_cidCommandId4 bytes插入和/或删除 CID 标记(与 t_xvac 重叠)
t_xvacTransactionId4 bytesXID 用于 VACUUM 操作移动行版本
t_ctidItemPointerData6 bytes此版本或更新版本的当前 TID
t_infomask2uint162 bytes属性数量,加上各种标志位
t_infomaskuint162 bytes各种标志位
t_hoffuint81 byte用户数据偏移

所有详细信息都可以在src/include/access/htup_details.h中找到。

只能使用从其他表(主要是pg_attribute)获得的信息来解释实际数据。标识字段位置所需的键值为attlenattalign。除了只有固定宽度的字段且没有空值时,否则无法直接获取特定属性。所有这些技巧都被包装在函数* heap_getattr fastgetattr heap_getsysattr *中。

要读取数据,您需要依次检查每个属性。首先根据空位图检查该字段是否为空。如果是,请转到下一个。然后确保对齐正确。如果该字段是固定宽度的字段,则所有字节都将被简单放置。如果它是一个可变长度的字段(attlen = -1),则要复杂一些。所有长度可变的数据类型都共享公共头结构struct varlena,其中包括存储值的总长度和一些标志位。根据标志的不同,数据可以是内联的或在 TOAST 表中。也可能会被压缩(请参阅Section 68.2)。


[14]实际上,索引访问方法不必使用此页面格式。现有的所有索引方法均使用此基本格式,但是保留在索引元页面上的数据通常不遵循项目布局规则。