ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 58.6\. 数据库分页文件 本节提供一个在PostgreSQL表和索引使用的页格式的概述。\[1\] 序列和TOAST表的格式就像一个普通表的。 下面说明一下,一个_字节_假定为包含8位。另外, 术语_项_为存储在页上的一个独立数据值。 在表中,一项是一行;在索引中,一项为一个索引条目。 每个表和索引存储为固定大小的_页_数组。 (通常 8 kB,不过当编译服务器的时候,可以选择不同的页大小) 在表中,所有的页是逻辑等价的,所以一个特殊项(行) 可以存储在任意页。在索引,第一页通常保留为持有控制信息的_元页_, 这里可以有不同类型的索引页,依赖于索引访问方法。 [Table 58-2](#calibre_link-2147)显示一个页的整体布局,这里每页有5部分。 **Table 58-2\. 页整体布局** | 项 | 描述 | | --- | --- | | PageHeaderData | 24字节长整型。包含关于页的一般信息,包含自由空间指针。 | | ItemIdData | 指向实际项的(偏移量,长度)数组对。每项4字节。 | | Free space | 未分配空间。从这个区域开始分配新项指针,或从结尾分配新项指针 | | Items | 实际项本身 | | Special space | 索引访问方法专用数据。不同方法存储不同的数据。普通表里为空。 | 每页的前24个字节构成一个页头(PageHeaderData)。 在[Table 58-3](#calibre_link-2148)有它的详细格式。 前两个字段跟踪相关页的最近的WAL条目。 下边的一个2字节的字段是包含标志位。 随后由3个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。 **Table 58-3\. PageHeaderData布局** | 字段 | 类型 | 长度 | 描述 | | --- | --- | --- | --- | | pd_lsn | XLogRecPtr | 8字节 | LSN: 该页上xlog日志记录变化的最后字节的下一字节 | | pd_tli | uint16 | 2字节 | 最后变化的时间线ID(仅其最低16位) | | 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`),每个需要4字节。 一个项标识符包含一个到项开始的字节偏移, 以字节计的长度,和一些影响它解释的属性位。 新项标识符需要从未分配空间的开始分配。 可以通过查看`pd_lower`来确定项标识符的数量,分配新的标示符, 其会增加。因为一个项标示符从来不移动直到释放了它,实际上, 每个指针为PostgreSQL所创建的一项由页号和项标识符的索引构成。 (`ItemPointer`,还可以称为`CTID`)。 项本身存储在从未分配的空间的结尾向后分配的空间。确切的结构取决于包含什么表。 表和序列两都使用一个名为`HeapTupleHeaderData`的结构,下面描述。 最后这段是"特殊段"其包含想存放的任何访问方法。 例如,b-tree索引存储连接页左右的兄弟,以及相应的索引结构的一些其它数据。 普通的表根本没有使用特殊段。 (通过设置`pd_special`等于页大小来表示) 所有表行结构方式相同。有个固定大小的头(在大多数机器占用23字节), 随后一个NULL位图的可选项,对象ID字段,和用户数据。 该头的详细信息在[Table 58-4](#calibre_link-2149)。 实际的用户数据(行中列)由`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 58-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.h`可以找到所有的详细信息。 解释实际数据只能从其它表获取信息来做,大多`pg_attribute`。 需要来表示字段位置的键值是`attlen`和`attalign`。 没有直接获取特定字段的方法,除仅当有固定宽度字段并且没有空值的情况外。 所有这些策略封装在函数_heap_getattr_,_fastgetattr_ 和_heap_getsysattr_。 要读取数据你需要逐次检查每个属性。首先检查字段是否为NULL依据NULL位图。 如果是,跳到下一个。然后确定你已经右对齐。如果字段是固定宽度的字段, 那么所有的字节简单的放置。如果它是变长的字段(attlen = -1) 那么它是一个更复杂的位。所有变长数据类型共享通用的头结构`struct varlena`, 其包括存储值的总长度和一些标志位。 依赖这些标志,数据可能是行内或在一个TOAST表;它也可能是压缩的。 (参阅[Section 58.2](#calibre_link-80)) ### Notes \[1\] 实际上,索引访问方法不需要使用这个页格式。所有已经存在的索引方法 需要使用基本格式,但是保持在索引元页上的数据通常不遵循项布局规则。