本节概述了PostgreSQL表和索引中使用的页面格式。[17] 序列和TOAST表的格式与普通表相同。
在下文的解释中,假设一个字节包含8位。此外,术语项指的是存储在页面上的单个数据值。在表中,项是一行;在索引中,项是一个索引条目。
每个表和索引都存储为一个固定大小的页面数组(通常为8kB,尽管在编译服务器时可以选择不同的页面大小)。在表中,所有页面在逻辑上都是等效的,因此特定项(行)可以存储在任何页面中。在索引中,第一个页面通常保留为存储控制信息的元页面,并且索引中可能存在不同类型的页面,具体取决于索引访问方法。
表 65.2显示了页面的总体布局。每个页面有五个部分。
表 65.2. 页面总体布局
项目 | 描述 |
---|---|
PageHeaderData | 24字节长。包含有关页面的常规信息,包括空闲空间指针。 |
ItemIdData | 指向实际项目的项目标识符数组。每个条目都是一个(偏移量,长度)对。每个项目4字节。 |
空闲空间 | 未分配的空间。新的项目标识符从该区域的开头分配,新项目从结尾分配。 |
项目 | 实际项目本身。 |
特殊空间 | 索引访问方法特定的数据。不同的方法存储不同的数据。在普通表中为空。 |
每个页面的前24个字节由页面头(PageHeaderData
)组成。其格式在表 65.3中详细说明。第一个字段跟踪与此页面相关的最新的WAL条目。如果启用了数据校验和,则第二个字段包含页面校验和。接下来是一个包含标志位的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。(在大多数这些版本中,基本的页面布局和头格式没有改变,但堆行头的布局已改变。)页面大小基本上只作为交叉检查存在;不支持在安装中使用多个页面大小。最后一个字段是一个提示,显示修剪页面是否可能有利可图:它跟踪页面上最旧的未修剪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. 页面布局
所有表行都以相同的方式构造。有一个固定大小的头(在大多数机器上占用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
)获得的信息。识别字段位置所需的键值是attlen
和attalign
。无法直接获取特定属性,除非只有固定宽度字段且没有空值。所有这些技巧都封装在函数heap_getattr、fastgetattr和heap_getsysattr中。
要读取数据,您需要依次检查每个属性。首先根据空位图检查字段是否为 NULL。如果是,则转到下一个。然后确保您有正确的对齐方式。如果字段是固定宽度字段,则所有字节都简单地放置。如果是可变长度字段 (attlen = -1),则情况会稍微复杂一些。所有可变长度数据类型共享公共的头部结构 struct varlena
,其中包括存储值的总长度和一些标志位。根据标志,数据可以是内联的,也可以在TOAST表中;它也可能被压缩(参见 第 65.2 节)。
如果您在文档中发现任何不正确的内容、与您对特定功能的体验不符的内容或需要进一步澄清的内容,请使用 此表单 报告文档问题。