2024年9月26日: PostgreSQL 17 发布!
支持的版本:当前 (17) / 16 / 15 / 14 / 13 / 12
开发版本:devel
不支持的版本:11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0

65.6. 数据库页面布局 #

本节概述了PostgreSQL表和索引中使用的页面格式。[17] 序列和TOAST表的格式与普通表相同。

在下文的解释中,假设一个字节包含8位。此外,术语指的是存储在页面上的单个数据值。在表中,项是一行;在索引中,项是一个索引条目。

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

表 65.2显示了页面的总体布局。每个页面有五个部分。

表 65.2. 页面总体布局

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

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

表 65.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),每个需要四个字节。项目标识符包含指向项目开始的字节偏移量、其字节长度以及一些影响其解释的属性位。根据需要从未分配空间的开头分配新的项目标识符。可以通过查看pd_lower来确定存在的项目标识符的数量,该值会增加以分配新的标识符。因为项目标识符在被释放之前永远不会移动,所以它的索引可以长期用于引用项目,即使项目本身在页面上移动以压缩空闲空间也是如此。实际上,PostgreSQL创建的每个指向项目的指针(ItemPointer,也称为CTID)都由页面号和项目标识符的索引组成。

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

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

图 65.1说明了这些部分如何在页面中布局。

图 65.1. 页面布局


65.6.1. 表行布局 #

所有表行都以相同的方式构造。有一个固定大小的头(在大多数机器上占用23个字节),后面跟着一个可选的空位图、一个可选的对象ID字段以及用户数据。头在表 65.4中详细说明。实际的用户数据(行的列)从t_hoff指示的偏移量开始,该偏移量必须始终是平台的MAXALIGN距离的倍数。仅当t_infomask中设置了HEAP_HASNULL位时,才会出现空位图。如果存在,它紧跟在固定头之后,并占用足够的字节以每个数据列有一个位(即,等于t_infomask2中的属性计数的位数)。在此位列表中,1位表示非空,0位表示空。当不存在位图时,假设所有列均非空。仅当t_infomask中设置了HEAP_HASOID_OLD位时,才会出现对象ID。如果存在,它出现在t_hoff边界之前。任何需要使t_hoff成为MAXALIGN倍数的填充都将出现在空位图和对象ID之间。(这反过来确保对象ID适当地对齐。)

表 65.4. HeapTupleHeaderData 布局

字段 类型 长度 描述
t_xmin TransactionId 4字节 插入XID戳记
t_xmax TransactionId 4字节 删除XID戳记
t_cid CommandId 4字节 插入和/或删除CID戳记(与t_xvac重叠)
t_xvac TransactionId 4字节 用于移动行版本的VACUUM操作的XID
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)获得的信息。识别字段位置所需的键值是attlenattalign。无法直接获取特定属性,除非只有固定宽度字段且没有空值。所有这些技巧都封装在函数heap_getattrfastgetattrheap_getsysattr中。

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



[17] 实际上,表或索引访问方法都不需要使用此页面格式。 heap 表访问方法始终使用此格式。所有现有的索引方法也使用基本格式,但存储在索引元数据页上的数据通常不遵循项目布局规则。

提交更正

如果您在文档中发现任何不正确的内容、与您对特定功能的体验不符的内容或需要进一步澄清的内容,请使用 此表单 报告文档问题。