选择 打开 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

受支持版本: 当前版本 (18) / 17 / 16 / 15 / 14
测试与开发版本: 19 / devel
不受支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0
历史版本PostgreSQL 9.1 已于 2016 年 10 月结束社区维护,本页译文保留供仍在使用旧版本的读者参考。新系统请看当前版本

55.6. 数据库页面布局 #

本节概述 PostgreSQL 表和索引内部所使用的页面格式。[12] 序列和 TOAST 表的格式与普通表相同。

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

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

表 55.2 展示了页面的总体布局。每个页面包含五个部分。

表 55.2. 总体页面布局

Item Description
PageHeaderData 24 bytes long. Contains general information about the page, including free space pointers.
ItemIdData Array of (offset,length) pairs pointing to the actual items. 4 bytes per item.
Free space The unallocated space. New item pointers are allocated from the start of this area, new items from the end.
Items The actual items themselves.
Special space Index access method specific data. Different methods store different data. Empty in ordinary tables.

每个页面的前 24 个字节由页头(PageHeaderData) 组成。它的格式详见 表 55.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。

表 55.3. PageHeaderData 布局

Field Type Length Description
pd_lsn XLogRecPtr 8 bytes LSN: next byte after last byte of xlog record for last change to this page
pd_tli uint16 2 bytes TimeLineID of last change (only its lowest 16 bits)
pd_flags uint16 2 bytes Flag bits
pd_lower LocationIndex 2 bytes Offset to start of free space
pd_upper LocationIndex 2 bytes Offset to end of free space
pd_special LocationIndex 2 bytes Offset to start of special space
pd_pagesize_version uint16 2 bytes Page size and layout version number information
pd_prune_xid TransactionId 4 bytes Oldest unpruned XMAX on page, or zero if none

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

页头之后是项标识符(ItemIdData),每个需要四个字节。 一个项标识符包含项起始位置的字节偏移量、其字节长度,以及若干影响解释方式的属性位。 新的项标识符会根据需要从未分配空间的起始处分配。当前已有多少个项标识符,可以通过查看 pd_lower 得知;分配新标识符时它会增加。因为一个项标识符在被释放之前永远不会移动, 所以即使项本身为了压缩空闲空间而在页面内移动,其索引仍然可以被长期用来引用该项。 事实上,每个指向项的指针(ItemPointer,也称 CTID) 在 PostgreSQL 中都由页号和项标识符索引构成。

项本身存储在从未分配空间末尾开始、向后分配的区域中。其确切结构取决于表要包含什么内容。 表和序列都使用一种名为 HeapTupleHeaderData 的结构体,如下所述。

最后一部分是特殊部分,其中可以存放访问方法希望保存的任何内容。例如, B-树索引会在这里保存指向页面左、右兄弟的链接,以及其他一些与索引结构相关的数据。 普通表完全不使用特殊部分(通过将 pd_special 设为页面大小来表示)。

所有表行的结构都相同。它们都有一个固定大小的头部(在大多数机器上占 23 字节), 后面跟着可选的空值位图、可选的对象 ID 字段以及用户数据。头部的详细格式见 表 55.4。实际用户数据(行的各列)从 t_hoff 指示的偏移位置开始,它必须始终是该平台 MAXALIGN 对齐单位的整数倍。只有当 HEAP_HASNULL 位在 t_infomask 中被置位时,空值位图才存在。若存在,它紧随固定头部之后, 并占用足够多的字节,以便为每个数据列提供一位 (也就是说,总共为 t_natts 位)。 在这个位图中,1 表示非空,0 表示空值。当位图不存在时,假定所有列都非空。 只有当 HEAP_HASOID 位在 t_infomask 中被置位时,对象 ID 才存在。若存在,它位于 t_hoff 边界之前。为了让 t_hoff 成为 MAXALIGN 的整数倍所需的任何填充,都会出现在空值位图与对象 ID 之间。 (这又反过来保证了对象 ID 的对齐是合适的。)

表 55.4. HeapTupleHeaderData 布局

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 insert and/or delete CID stamp (overlays with t_xvac)
t_xvac TransactionId 4 bytes XID for VACUUM operation moving a row version
t_ctid ItemPointerData 6 bytes current TID of this or newer row version
t_infomask2 int16 2 bytes number of attributes, plus various flag bits
t_infomask uint16 2 bytes various flag bits
t_hoff uint8 1 byte offset to user data

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

要解释实际数据,必须借助从其他表中获得的信息,其中大部分来自 pg_attribute。识别字段位置所需的关键值是 attlenattalign。 除非所有字段都是定宽且没有空值,否则没有办法直接取得某个特定属性。 所有这些技巧都封装在 heap_getattrfastgetattrheap_getsysattr 这些函数中。

读取数据时,需要依次检查每个属性。首先根据空值位图判断该字段是否为 NULL。 如果是,就继续下一个。然后确认对齐是否正确。如果字段是定宽字段,那么它的所有字节 都是直接摆放的;如果它是变长字段(attlen = -1),情况就会稍复杂一些。所有变长 数据类型都共享一个通用头部结构体 struct varlena,其中包含已存储值的 总长度以及一些标志位。根据这些标志,数据可能是线内存储的,也可能位于 TOAST 表中;它也可能是经过压缩的(见 第 55.2 节)。



[12] 实际上,索引访问方法不要求必须使用这种页面格式。 现有的所有索引方法都使用这种 基本格式,但索引元页中保存的数据通常并不遵循项布局规则。

提交更正

译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。