Postgresql 中文操作指南
73.6. Database Page Layout #
本节概述了 PostgreSQL 表和索引中使用的页面格式。 [17 ] 序列和 TOAST 表的格式与普通表相同。
在以下说明中,假定一个 byte 包含 8 位。此外,术语 item 指存储在页面上的单独数据值。在表中,项目是一行;在索引中,项目是一个索引条目。
每个表和索引都存储为一个 pages 数组,该数组具有一个固定的尺寸(通常为 8 kB,尽管在编译服务器时可以选择不同的页面尺寸)。在表中,所有页面在逻辑上都是等效的,因此可以将特定项目(行)存储在任何页面中。在索引中,第一个页面通常保留为 metapage,以保存控制信息,并且索引中可以有不同类型的页面,这取决于索引访问方法。
Table 73.2显示了页面的整体布局。每个页面有五个部分。
Table 73.2. Overall Page Layout
Item |
Description |
PageHeaderData |
长度为 24 个字节。包含有关页面的常规信息,包括空闲空间指针。 |
ItemIdData |
指向实际项目的项目标识符数组。每个条目都是 (偏移量、长度) 对。每项 4 个字节。 |
Free space |
未分配的空间。从该区域的开头分配新的项目标识符,从结尾分配新的项目。 |
Items |
The actual items themselves. |
Special space |
索引访问方法特定数据。不同的方法会存储不同的数据。在普通表中为空。 |
每个页面的前 24 个字节包含页面头(PageHeaderData)。其格式在 Table 73.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。(在这些版本中,基本页面布局和头格式没有发生改变,但是堆行头的布局发生了改变。)页面大小基本上只是作为一个交叉检查存在;在一个安装中,不支持拥有多个页面大小。最后一个字段是一个提示,表示整理页面是否有利可图:它跟踪页面上最旧的未整理 XMAX。
Table 73.3. PageHeaderData Layout
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 等于页面大小来指示)。
Figure 73.1说明了这些部分如何在页面中布置。
Figure 73.1. Page Layout
73.6.1. Table Row Layout #
所有的表行以相同的方式进行结构化。有一个固定大小的表头(在大多数机器上占用 23 个字节),后边是可选的空位图、可选的对象 ID 字段以及用户数据。详细介绍了表头 Table 73.4 中的内容。实际的用户数据(行的列)从 t_hoff 指示的偏移处开始,该偏移处必须始终是平台的 MAXALIGN 距离的倍数。仅当在 t_infomask 中设置 HEAP_HASNULL 位时,才会出现空位图。如果存在空位图,它会紧跟固定表头之后并占用足以有每列数据对应一个位(即,等于 t_infomask2 中属性数目的位数)的字节。在此位列表中:1 位表示非空,0 位表示空。当不存在位图时,假定所有列是非空的。仅当在 t_infomask 中设置 HEAP_HASOID_OLD 位时,才会出现对象 ID。如果存在对象 ID,它会紧挨 t_hoff 边界之前出现。为使 t_hoff 成为 MAXALIGN 倍数所需的任何填充内容将显示在空位图和对象 ID 之间。(反过来,这将确保对象 ID 的对齐方式合适。)
Table 73.4. HeapTupleHeaderData Layout
Field |
Type |
Length |
Description |
t_xmin |
TransactionId |
4 bytes |
insert XID stamp |
t_xmax |
TransactionId |
4 bytes |
delete XID stamp |
t_cid |
CommandId |
4 bytes |
插入和/或删除 CID 戳(与 t_xvac 重叠) |
t_xvac |
TransactionId |
4 bytes |
移动行版本的 VACUUM 操作的 XID |
t_ctid |
ItemPointerData |
6 bytes |
此行版本或更新版本的当前 TID |
t_infomask2 |
uint16 |
2 bytes |
属性数量加上各种标志位 |
t_infomask |
uint16 |
2 bytes |
various flag bits |
t_hoff |
uint8 |
1 byte |
offset to user data |
所有详细内容都可以在 src/include/access/htup_details.h 中找到。
解释实际数据只能使用从其他表中获得的信息进行,主要为 pg_attribute。识别字段位置所需要的关键值是 attlen 和 attalign。除了只有固定宽度字段且没有任何空值的情况下以外,没有办法直接获得特定属性。所有这些技巧都封装在函数 heap_getattr、fastgetattr 和 heap_getsysattr 中。
为了读取数据,你需要依次检查每个属性。首先,根据空值位图检查字段是否为空。如果它是空的,则转到下一个字段。然后,确保您具有正确的对齐方式。如果该字段是固定宽度字段,那么所有字节都将简单地放置。如果该字段是可变长度字段(attlen = -1),那么它将更加复杂。所有可变长度数据类型共享通用的标头结构 struct varlena,其中包括存储值的总长度和一些标志位。根据标志,数据可以是内嵌的,也可以是在 TOAST 表中;它也可以被压缩(参见 Section 73.2)。
[17 ] 实际上,表或索引访问方法并不需要使用这种页面格式。 heap 表访问方法始终使用此格式。所有现有的索引方法也使用基本格式,但索引元页面上保存的数据通常不遵循条目布局规则。