Chapter 7. 分页文件

一份描述数据库文件缺省页面格式的文档.

本章提供一个PostgreSQL 的表和索引所使用的页面格式的概述.(索引访问模式不 需要使用这些页面格式。 目前,所有索引方法都使用这个基本格式,但保留在索引元数据页里 的数据通常并不准确地遵循项布局规则。)TOAST表和序列的格式和 普通表一样。

在下面解释中,假定一个 字节 包含 8 个位.另外, 项(item) 指的是存储在一个页面里的独立数据值。在一个表里, 一个项是一个元组(行);在一个索引里,一个项是一条索引记录。

Table 7-1 显示一个页面的基本布局。每个页面有五个部分。

Table 7-1. 样例页面布局

描述
PageHeaderData20字节长。包含关于页面的一般信息, 包括自由空间指针。
ItemPointerData(偏移量,长度)对的数组,指向实际项。
Free space(自由空间)未分配的空间。所有新元组都从这里分配,通常是从结尾开始。
Items(项)实际的项本身。
Special Space(特殊空间)索引访问模式相关的数据。不同的方式存放不同的数据。 在普通表中为空。

每个页面的头20个字节组成页头(PageHeaderData)。它的格式在 Table 7-2 里详细介绍。头两个字节 处理与 WAL 相关的东西。然后跟着三个2字节的整数字段 (pd_lowerpd_upper, 和 pd_special)。这些字段分别表示与未分配 空间开头的偏移,与未分配空间结尾的偏移,以及到特殊空间开头的偏移。

Table 7-2. PageHeaderData 布局

字段类型长度描述
pd_lsnXLogRecPtr8 字节LSN: xlog 最后一个字节的下一个字节
pd_suiStartUpID4 字节最后修改的 SUI (目前它只用于堆 AM)
pd_lowerLocationIndex2 字节到自由空间开头的偏移量。
pd_upperLocationIndex2 字节到自由空间结尾的偏移量。
pd_specialLocationIndex2 字节到特殊空间开头的偏移量。
pd_pagesize_versionuint162 字节页面大小和布局版本号信息。

所有细节都可以在 src/include/storage/bufpage.h 里找到。

特殊空间是在页面末尾的区域,它在页面初始化的时候分配,包含与某种 访问方式相关的信息。页头的最后两个字节,pd_pagesize_version, 保存页面大小和一个版本指示器。从 PostgreSQL 7.3 开始, 版本数是 1;以前的版本使用版本号 0。(基本的页面布局以及头格式没有变化, 但是堆元组头的布局改变了。)页面大小通常只用作交叉检查;在一次安装中 没有大于一个页面尺寸的支持。

在页头后面是项标识符(ItemIdData),每个需要四个字节。 一个项标识符包含一个到项开头的字节偏移量,它自己以字节计的长度, 以及一个属性位的集合,这些属性位影响它的解释。新的项标识符根据需要 从未分配空间的开头分配。项标识符的数目可以通过查看 pd_lower 来判断,在分配新标识符的时候会递增。因为一个项标识符在其释放前绝对 不会移动,所以它的索引可以用于长时间地引用一个项,即使该项本身因为 压缩自由空间在页面内部进行了移动也如此。实际上,PostgreSQL 创建地每个指向项的指针(ItemPointer,也叫做 CTID) 都由一个页号和一个项标识符的索引组成。

项本身存储在从未分配空间末尾开始从后向前分配的空间里。它们的实际 结构因表包含的内容不同而不同。表和序列都使用一种叫做 HeapTupleHeaderData 的结构,在下面描述。

最后一段是“特殊段”,它可以包含任何访问方法想存放的东西。 普通表并不使用这个段(通过设置 pd_special 来指示,以 平衡页面大小)。

所有表元组都用同样方法构造。它们有一个定长的头(在大多数机器上占据23个字节), 后面跟着一个可选的 null 位图,一个可选的对象 ID 字段,以及用户数据。 头在 Table 7-3 里详细描述。实际用户数据 (元组的字段)从 t_hoff 标识的偏移量开始,它必须是 该平台的 MAXALIGN 距离的倍数。null 位图只有在 t_infomask 里面的 HEAP_HASNULL 位设置了的时候才出现。 如果它出现了,那么它紧跟在定长头后面,占据足够容纳每个数据字段对应一个位的字节数 (也就是说,总共 t_natts 位)。在这个位列里面,为 1 的位 表示非空,而为 0 的位表示空。如果没有出现这个位图,那么所有数据字段都 假设为非空的。对象 ID 只有在设置了 t_infomask 里面的 HEAP_HASOID 位的时候才出现。如果出现,它正好出现在 t_hoff 范围之前。如果需要补齐 t_hoff,使之 成为 MAXALIGN 的倍数,那么这些填充将出现在 null 位图和度相 ID 之间。 (这样也保证了对象 ID 得到恰当的对齐。)

Table 7-3. HeapTupleHeaderData 布局

字段类型长度描述
t_xminTransactionId4 字节插入 XID 戳记
t_cminCommandId4 字节插入 CID 戳记(和 t_xmax 重叠)
t_xmaxTransactionId4 字节删除 XID 戳记
t_cmaxCommandId4 字节删除 CID 戳记(与 t_xvac 重叠)
t_xvacTransactionId4 字节用于 VACUUM 操作移动元组的 XID
t_ctidItemPointerData6 字节这个或者新元组的当前 ID
t_nattsint162 字节字段数目
t_infomaskuint162 字节各种标志
t_hoffuint81 字节到用户数据的偏移量

所有细节都可以在 src/include/access/htup.h 中找到。

对具体数据的解释只能在从其它表中获取的信息的情况下进行, 这些信息大多数在 pg_attribute 里。 尤其是 attlen 字段和 attalign 字段。 我们没有办法直接获取某个字段,除非它们是定宽并且没有 NULL 的。 所有这些复杂的操作都封装在函数 heap_getattrfastgetattrheap_getsysattr 里。

要读取数据的话,你需要轮流检查每个字段。首先根据 null 位图检查该字段是否为 NULL。 如果是,那么跳到下一个字段。然后保证你的对齐是正确的。如果字段是一个 定宽字段,那么所有字节都简单地放在那里。如果它是一个变长字段(attlen == -1), 那么它就会更加复杂一些,它会使用变长结构 varattrib。 根据标志地不同,数据可能是内联的,也可能是压缩的或者是在其它表中(TOAST)。