On this page
66.6. 数据库页面布局
本节概述了 PostgreSQL 表和索引中使用的页面格式。 [14]序列和 TOAST 表的格式类似于常规表。
在下面的说明中,假定* byte 包含 8 位。另外,术语项目*是指存储在页面上的单个数据值。在表中,一项是一行;在索引中,项目是索引条目。
每个表和索引都存储为固定大小的* page 数组(通常为 8 kB,尽管在编译服务器时可以选择不同的页面大小)。在一个表中,所有页面在逻辑上都是等效的,因此特定项目(行)可以存储在任何页面中。在索引中,通常将第一页保留为 metapage *来保存控制信息,并且根据索引访问方法,索引内可以有不同类型的页面。
Table 66.2显示页面的总体布局。每页分为五个部分。
表 66.2. 整体页面布局
Item | Description |
---|---|
PageHeaderData | 24 个字节长。包含有关页面的常规信息,包括可用空间指针。 |
ItemIdData | 指向实际项目的项目标识符数组。每个条目都是一个(偏移,长度)对。每个项目 4 个字节。 |
Free space | 未分配的空间。从该区域的开始分配新的项目标识符,从结尾开始分配新的项目。 |
Items | 实际项目本身。 |
Special space | 索引访问方法特定的数据。不同的方法存储不同的数据。在普通表中为空。 |
每页的前 24 个字节由页眉(PageHeaderData
)组成。其格式在Table 66.3中详细说明。第一个字段跟踪与此页面相关的最新 WAL 条目。如果启用了data checksums,则第二个字段包含页面校验和。接下来是一个包含标志位的 2 字节字段。其后是三个 2 字节整数字段(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.(在大多数这些版本中,基本页面布局和 Headers 格式没有更改,但堆行 Headers 的布局已更改.)页面大小基本上仅作为交叉检查出现;不支持安装中的页面大小超过一个。最后一个字段是一个提示,显示修剪页面是否可能有利可图:它跟踪页面上最早的未修剪 XMAX。
表 66.3. PageHeaderData 布局
Field | Type | Length | Description |
---|---|---|---|
pd_lsn | PageXLogRecPtr | 8 bytes | LSN:WAL 记录的最后一个字节之后的下一个字节,用于此页面的最后更改 |
pd_checksum | uint16 | 2 bytes | Page checksum |
pd_flags | uint16 | 2 bytes | Flag bits |
pd_lower | LocationIndex | 2 bytes | 偏移到可用空间的开始 |
pd_upper | LocationIndex | 2 bytes | 偏移到可用空间的尽头 |
pd_special | LocationIndex | 2 bytes | 偏移到特殊空间的开始 |
pd_pagesize_version | uint16 | 2 bytes | 页面大小和布局版本号信息 |
pd_prune_xid | TransactionId | 4 bytes | 页面上最旧的未修剪 XMAX,如果没有,则为零 |
所有详细信息都可以在src/include/storage/bufpage.h
中找到。
页面标题后面是项目标识符(ItemIdData
),每个标识符都需要四个字节。项标识符包含项开头的字节偏移量,项的长度(以字节为单位)和一些影响项解释的属性位。从未分配空间的开头开始,根据需要分配新的项目标识符。可以通过查看pd_lower
来确定存在的项目标识符的数量,将其增加以分配新的标识符。因为项目标识符直到被释放才被移动,所以即使项目本身在页面上四处移动以压缩可用空间,它的索引也可以长期用于引用项目。实际上,由 PostgreSQL 创建的指向项目(ItemPointer
,也称为CTID
)的每个指针都由页码和项目标识符的索引组成。
项目本身存储在从未分配空间的末尾向后分配的空间中。确切的结构取决于表所包含的内容。表和序列都使用名为HeapTupleHeaderData
的结构,如下所述。
最后一部分是“特殊部分”,其中可以包含访问方法希望存储的任何内容。例如,b 树索引存储到页面的左右同级的链接,以及与索引结构相关的其他一些数据。普通表根本不使用特殊节(通过将pd_special
设置为等于页面大小来表示)。
所有表行的结构均相同。有一个固定大小的 Headers(在大多数计算机上占 23 个字节),后跟一个可选的空位图,一个可选的对象 ID 字段以及用户数据。标题在Table 66.4中详细说明。实际用户数据(行的列)从t_hoff
指示的偏移量开始,该偏移量必须始终是平台的 MAXALIGN 距离的倍数。只有在t_infomask
中设置了* HEAP_HASNULL 位时,才出现空位图。如果存在,它将在固定头之后立即开始,并且占用足够的字节以使每个数据列具有一位(即,总共t_natts
位)。在此位列表中,1 位表示非空,0 位表示空。当位图不存在时,所有列均假定为非空。仅当在t_infomask
中设置 HEAP_HASOID *时,才存在对象 ID。如果存在,它将出现在t_hoff
边界之前。在空位图和对象 ID 之间将出现使t_hoff
为 MAXALIGN 倍数所需的任何填充。 (这反过来又确保了对象 ID 正确对齐.)
表 66.4. HeapTupleHeaderData 布局
Field | Type | Length | Description |
---|---|---|---|
t_xmin | TransactionId | 4 bytes | 插入 XID 戳 |
t_xmax | TransactionId | 4 bytes | 删除 XID 图章 |
t_cid | CommandId | 4 bytes | 插入和/或删除 CID 标记(与 t_xvac 重叠) |
t_xvac | TransactionId | 4 bytes | XID 用于 VACUUM 操作移动行版本 |
t_ctid | ItemPointerData | 6 bytes | 此版本或更新版本的当前 TID |
t_infomask2 | uint16 | 2 bytes | 属性数量,加上各种标志位 |
t_infomask | uint16 | 2 bytes | 各种标志位 |
t_hoff | uint8 | 1 byte | 用户数据偏移 |
所有详细信息都可以在src/include/access/htup_details.h
中找到。
只能使用从其他表(主要是pg_attribute
)获得的信息来解释实际数据。标识字段位置所需的键值为attlen
和attalign
。除了只有固定宽度的字段且没有空值时,否则无法直接获取特定属性。所有这些技巧都被包装在函数* heap_getattr , fastgetattr 和 heap_getsysattr *中。
要读取数据,您需要依次检查每个属性。首先根据空位图检查该字段是否为空。如果是,请转到下一个。然后确保对齐正确。如果该字段是固定宽度的字段,则所有字节都将被简单放置。如果它是一个可变长度的字段(attlen = -1),则要复杂一些。所有长度可变的数据类型都共享公共头结构struct varlena
,其中包括存储值的总长度和一些标志位。根据标志的不同,数据可以是内联的或在 TOAST 表中。也可能会被压缩(请参阅Section 66.2)。
[14]实际上,索引访问方法不必使用此页面格式。现有的所有索引方法均使用此基本格式,但是保留在索引元页面上的数据通常不遵循项目布局规则。