PostreSQL-内幕探索-学习-02-堆文件数据页介绍

发布于:2025-08-31 ⋅ 阅读:(19) ⋅ 点赞:(0)

目录

一、环境信息

二、参考内容

三、数据文件

四、堆文件页面

1、图示

2、PageHeaderData结构体

3、pd_flags相关宏

4、PageXLogRecPtr结构体

5、ItemIdData结构体

6、lp_flags相关宏

7、HeapTupleHeaderData结构体

8、HeapTupleFields结构体

9、DatumTupleFields结构体

10、ItemPointerData结构体

11、BlockIdData结构体

12、t_infomask2相关宏

13、t_infomask相关宏


一、环境信息

名称
CPU 12th Gen Intel(R) Core(TM) i7-12700H
操作系统 CentOS Linux release 7.9.2009 (Core)
内存 3G
逻辑核数 2
PG版本 9.6.24

二、参考内容

参考内容
《PostreSQL指南:内幕探索》
PostgreSQL: Documentation: 9.6: PostgreSQL 9.6.24 Documentation

三、数据文件

数据文件例如:堆表、索引、空闲空间映射、可见性映射等,内部会被划分成多个固定长度的数据页,数据页大小通常大小为8KB,但也有其他情况如:16KB,32KB。每个文件中的页从0开始顺序编号,编号可以称为页号或区块号。

四、堆文件页面

1、图示

每一个PAGE可以转换为一个PageHeaderData结构体。

2、PageHeaderData结构体

typedef struct PageHeaderData
{
	/* XXX LSN is member of *any* block, not only page-organized ones */
	PageXLogRecPtr pd_lsn;		                                                         
	uint16		   pd_checksum;	                                                       
	uint16		   pd_flags;		              
	LocationIndex  pd_lower;		              
	LocationIndex  pd_upper;		              
	LocationIndex  pd_special;	                  										
	uint16		   pd_pagesize_version;           									
	TransactionId  pd_prune_xid;                                                
	ItemIdData	   pd_linp[FLEXIBLE_ARRAY_MEMBER];
} PageHeaderData, *PageHeader;
参数 说明
pd_lsn 记录最后一次修改该页面的 WAL(Write-Ahead Log)记录的 LSN。即当前的WAL位置。
pd_checksum 即校验和,为每个数据页计算校验和。检测到校验和失败将导致读取数据时出错,并将中止当前正在运行的事务。因此,这为直接在数据库服务器级别检测I/O或硬件问题带来了额外的控制。
pd_flags 用于记录页面的状态标志。
pd_lower 空闲空间的起始偏移量。
pd_upper 空闲空间的结束偏移量。
pd_special 特殊空间开始位置。
在索引页中会用到该字段,在堆表页中它指向页尾。(在索引页中它指向特殊空间的起始位置,特殊空间是仅由索引使用的特殊数据区域,包含特定的数据,具体内容依索引的类型而定,如B树、GiST、GiN等。)
pd_pagesize_version 用于管理页面的版本和大小信息,确保数据存储的兼容性和完整性。
页面版本编号与页面尺寸被打包成了单个uint16字段,这是有历史原因的:在PostgreSQL7.3之前,并没有页面版本编号这个概念,这样做能让我们假装7.3之前版本的页面版本编号为0。我们约定页面的尺寸必须为256的倍数,留下低8位用于页面版本编号。
pd_prune_xid 最旧的可修剪(删除)的XID,是一个提示字段,用于帮助确认剪枝是否有用。目前索引页没用。
pd_linp 行指针,是一个柔性数组。

C语言柔性数组相关介绍可以参考之前的博客:《C语言学习-24-柔性数组》;

3、pd_flags相关宏

#define PD_HAS_FREE_LINES	0x0001
#define PD_PAGE_FULL		0x0002
#define PD_ALL_VISIBLE		0x0004
#define PD_VALID_FLAG_BITS	0x0007
描述
PD_HAS_FREE_LINES 页面中存在空闲行指针(line pointers)。
PD_PAGE_FULL 页面已被标记为“满”(空间不足,需优先跳过扫描)。
PD_ALL_VISIBLE 页面所有元组对事务均可见(用于可见性映射优化)。
PD_VALID_FLAG_BITS 保留位(表示当前版本支持的有效标志位总数)。

4、PageXLogRecPtr结构体

typedef struct
{
    uint32		xlogid;			/* high bits */
    uint32		xrecoff;		/* low bits */
} PageXLogRecPtr;
字段名 说明
xlogid 日志文件段编号的高位部分
xrecoff 日志文件段内的偏移量(低位部分)

5、ItemIdData结构体

typedef struct ItemIdData    
{
	unsigned	lp_off:15,	
				lp_flags:2,	
				lp_len:15;	
} ItemIdData, *ItemId;

此结构体使用的是C语言位域,其特点和工作原理如下:

名称 描述
内存布局 这三个成员总共占用 15 + 2 + 15 = 32 位,即4字节,它们被打包在一个无符号整数(通常是32位)中,具体布局顺序(哪个成员在高位,哪个在低位)取决于编译器和平台。
空间优化 位域允许在单个机器字中存储多个小范围的值,相比使用三个独立的整数字段(可能占用12字节),这种方法只需4字节。
访问方式 可以像普通结构体成员一样访问位域成员,编译器会自动处理位的提取和设置。
参数 说明
lp_off 元组在文件块中的偏移量(从页头开始计算)。
lp_flags 元组的状态。
lp_len 元组的字节长度。

6、lp_flags相关宏

#define LP_UNUSED		        0		            /* unused (should always have lp_len=0) */
#define LP_NORMAL		        1		            /* used (should always have lp_len>0) */
#define LP_REDIRECT		        2		            /* HOT redirect (should have lp_len=0) */
#define LP_DEAD			        3		            /* dead, may or may not have storage */
描述
LP_UNUSED 未使用(lp_len必须始终为0) 。
LP_NORMAL 正常使用 (lp_len 必须始终>0)。
LP_REDIRECT HOT 重定向 (lp_len必须为0)。
LP_DEAD 死元组,有没有对应的存储尚未可知。

7、HeapTupleHeaderData结构体

typedef struct HeapTupleHeaderData
{
	union
	{
		HeapTupleFields  t_heap;
		DatumTupleFields t_datum;
	}t_choice;                 
	ItemPointerData t_ctid;		
	uint16		    t_infomask2;	
	uint16		    t_infomask;		
	uint8		    t_hoff;			
	bits8		    t_bits[FLEXIBLE_ARRAY_MEMBER];	/* bitmap of NULLs */

	/* MORE DATA FOLLOWS AT END OF STRUCT */
}HeapTupleHeaderData, *HeapTupleHeader;
参数

说明

t_heap 用于记录对元组执行插入或删除操作的事务ID和命令ID,这些信息主要用于并发控制时检查元组对事务的可见性。
t_datum 记录元组的长度等信息。
t_ctid 元组指针,用于记录当前元组或者新元组的物理位置(块内偏移量和元组长度),若元组被更新,则记录的是新版本元组的物理位置。
t_infomask2 使用其低11位表示当前元组的属性个数,其他位则用于HOT技术以及元组可见性的标志位。
t_infomask 用于标识元组当前的状态,比如元组是否具有OID、是否有空属性等,其每一位对应不同的状态,共16种状态。
t_hoff 元组头的大小。
t_bits 表示元组那些字段为空。


8、HeapTupleFields结构体

专门用于管理事务可见性和元组生命周期,是实现 MVCC (多版本并发控制) 的基石。

 typedef struct HeapTupleFields
 {
     TransactionId t_xmin;		
     TransactionId t_xmax;		
 
     union
     {
         CommandId	t_cid;		
         TransactionId t_xvac;	
     }			t_field3;
 } HeapTupleFields;
字段名 说明
t_xmin 插入事务ID
• 记录成功插入此元组的事务的唯一标识符。
• 对于一个事务来说,只有当 t_xmin 对应的事务已经提交,并且其提交时间点早于当前事务的快照时间,这个元组才对当前事务可见。
t_xmax 删除/锁定事务ID
• 默认值為 0,表示该元组尚未被删除或锁定。
• 如果非零,则表示:
  - 删除: 某个事务删除了此元组。该事务提交后,此元组应被视作无效(但可能由于MVCC而对某些事务仍可见)。
  - 更新: 更新操作在PG中等于删除+插入。t_xmax 记录了删除旧版本的事务ID。
  - 行锁: 一个事务锁定了此行(例如 SELECT ... FOR UPDATE)。在该事务结束(提交或回滚)前,t_xmax 会一直持有该事务ID。
t_field3 一个联合体(共用体),其含义取决于元组的当前状态(主要由 t_xmax 的值决定):
• 当 t_xmax == 0 (元组有效)
  - t_cid (CommandId): 命令ID。表示在插入事务(t_xmin)中,是第几个SQL命令插入了该元组(从0开始计数)。用于在同一事务内确定操作的先后顺序。
• 当 t_xmax != 0 (元组被无效化)
  - t_xvac (TransactionId): 清理事务ID。通常由 VACUUM FULL 操作设置。表示此元组是由一个 VACUUM FULL 事务移动或冻结的旧版本元组。这是一个相对少见的特殊用途字段。

9、DatumTupleFields结构体

typedef struct DatumTupleFields
{
	int32		datum_len_;		
	int32		datum_typmod;	
	Oid			datum_typeid;	
} DatumTupleFields;
参数 描述
datum_len_ Datum 数据长度
• 这是 varlena 类型数据的头部信息,存储数据的长度
• 在 PostgreSQL 中,varlena 是可变长度数据的内部表示格式
datum_typmod 类型修饰符
• 用于提供数据类型的附加信息
• 值为 -1 表示没有类型修饰符
• 对于记录类型,这是一个标识符
datum_typeid 数据类型对象标识符
• 存储数据类型的 OID(对象标识符)
• 对于复合类型,存储复合类型的 OID
• 如果是记录类型,则存储特殊的 RECORDOID
• OID 是 PostgreSQL 内部用于唯一标识系统表中对象的数字

场景 DatumTupleFields 是否有效? datum_typeid 的含义 datum_len_ 的含义
普通堆元组(Heap Tuple) 部分有效 通常无效/无意义。内存被 HeapTupleFields.t_field3 占用。 有效。被借用,存储所有变长字段的数据总长度
TOAST 指针 完全有效 有效。值为 TIDOID,表明这是一个指向TOAST数据的指针。 有效。存储这个TOAST指针结构本身的长度。
匿名记录类型 完全有效 有效。值为 RECORDOID,表明这是一个匿名记录。 有效。存储这个记录数据的长度。

10、ItemPointerData结构体

typedef struct ItemPointerData
{
    BlockIdData  ip_blkid;
    OffsetNumber ip_posid;
}ItemPointerData;

字段名 说明
ip_blkid 块标识符
• 指定元组所在的磁盘块(页面)
• BlockIdData 内部包含 bi_hi 和 bi_lo 字段,共同表示 32 位块编号
ip_posid 第几行TUPLE,元组在行指针数组中的索引号/序号,1, 2, 3, ... (从1开始的整数序号)。

11、BlockIdData结构体

typedef struct BlockIdData
{
    uint16		bi_hi;                             /*块编号的高 16 位。*/
    uint16		bi_lo;                             /*块编号的低 16 位。*/
} BlockIdData;
字段名 说明
bi_hi 块编号的高 16 位
bi_lo 块编号的低 16 位

BlockIdGetBlockNumber(blockId): 从 BlockIdData 结构提取出完整的 32 位块号。

/*
 * PointerIsValid
 *		True iff pointer is valid.
 */
#define PointerIsValid(pointer) ((const void*)(pointer) != NULL)


/*
 * BlockIdIsValid
 *		True iff the block identifier is valid.
 */
#define BlockIdIsValid(blockId) \
	((bool) PointerIsValid(blockId))


/*
 * BlockIdGetBlockNumber
 *		Retrieve the block number from a block identifier.
 */
#define BlockIdGetBlockNumber(blockId) \
( \
	AssertMacro(BlockIdIsValid(blockId)), \
	(BlockNumber) (((blockId)->bi_hi << 16) | ((uint16) (blockId)->bi_lo)) \
)

12、t_infomask2相关宏

#define HEAP_NATTS_MASK			0x07FF	
#define HEAP_KEYS_UPDATED		0x2000	
#define HEAP_HOT_UPDATED		0x4000	
#define HEAP_ONLY_TUPLE			0x8000	
#define HEAP2_XACT_MASK			0xE000	
说明
HEAP_NATTS_MASK 属性数量掩码
• 用于从 t_infomask2 字段中提取该元组所包含的属性(列)的数量
• 它覆盖了 t_infomask2 的低 11 位(2^11 - 1 = 2047),这意味着一个元组最多可拥有 2047 个列(实际限制可能更小,受页面大小等因素制约)。
HEAP_KEYS_UPDATED 键被更新标志
• 如果设置此标志位,表示此元组是由一个 UPDATE 操作产生的,并且该 UPDATE 修改了至少一个索引键的列
• 这对于索引维护至关重要。当此标志被设置时,数据库知道它必须删除旧的索引条目并插入新的条目,即使该 UPDATE 可能是一个HOT(Heap Only Tuple)更新。
• 它帮助优化索引维护流程。
HEAP_HOT_UPDATED HOT更新目标标志
• 此标志设置在旧版本的元组上。
• 如果设置,表示该元组已被后续的 HOT更新,并且在这个旧元组之后同一个数据页内存在一个更新的版本。
• 它本质上是一个指针,告诉 vacuum 和索引扫描等操作:“要找到这个元组的最新版本,不必去查索引,可以直接在同一页面上寻找更晚的版本。”
HEAP_ONLY_TUPLE 仅堆元组标志
• 如果设置,表示此元组是一个 HOT 元组
• 这意味着该元组是某个更新操作产生的新版本,并且由于更新没有修改任何索引键,因此这个新版本没有额外的索引条目指向它。索引仍然指向原始的行版本(即行的开头)。
• 设置此标志的元组只能通过基于旧版本元组的“HOT链”来找到,而不能通过索引直接定位。
HEAP2_XACT_MASK 事务信息掩码(已废弃)
• 这是一个历史遗留的掩码,在现代版本的 PostgreSQL 中已不再使用
• 它最初的设计目的是为了从 t_infomask2 中提取一些事务相关信息,但其功能早已被转移到 t_infomask 字段中。
• 现在,这个掩码所覆盖的位(高 3 位)被用于上述的三个独立标志:KEYS_UPDATEDHOT_UPDATED, 和 ONLY_TUPLE
• 重要提示:在你的代码中,不应该使用这个掩码,而应该直接使用各个独立的标志位宏。

13、t_infomask相关宏

#define HEAP_HASNULL			0x0001
#define HEAP_HASVARWIDTH		0x0002
#define HEAP_HASEXTERNAL		0x0004
#define HEAP_HASOID				0x0008
#define HEAP_XMAX_KEYSHR_LOCK	0x0010
#define HEAP_COMBOCID			0x0020
#define HEAP_XMAX_EXCL_LOCK		0x0040
#define HEAP_XMAX_LOCK_ONLY		0x0080

 /* xmax is a shared locker */
#define HEAP_XMAX_SHR_LOCK	    (HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_KEYSHR_LOCK)
#define HEAP_LOCK_MASK	        (HEAP_XMAX_SHR_LOCK | HEAP_XMAX_EXCL_LOCK | HEAP_XMAX_KEYSHR_LOCK)
#define HEAP_XMIN_COMMITTED		0x0100	
#define HEAP_XMIN_INVALID		0x0200	
#define HEAP_XMIN_FROZEN		(HEAP_XMIN_COMMITTED|HEAP_XMIN_INVALID)
#define HEAP_XMAX_COMMITTED		0x0400	
#define HEAP_XMAX_INVALID		0x0800	
#define HEAP_XMAX_IS_MULTI		0x1000	
#define HEAP_UPDATED			0x2000	
#define HEAP_MOVED_OFF			0x4000									
#define HEAP_MOVED_IN			0x8000					
#define HEAP_MOVED              (HEAP_MOVED_OFF | HEAP_MOVED_IN)
#define HEAP_XACT_MASK			0xFFF0	
宏名称 详细解释
HEAP_HASNULL 表示元组中包含至少一个 NULL 值。如果设置,元组头部后会有一个空值位图指示哪些列为 NULL。
HEAP_HASVARWIDTH 表示元组中包含至少一个变长字段(如 TEXT、VARCHAR、JSONB 等)。如果设置,元组头部会借用 DatumTupleFields 结构。
HEAP_HASEXTERNAL 表示元组中包含至少一个外部存储(TOAST)的字段。这些字段的值存储在独立的 TOAST 表中。
HEAP_HASOID 表示元组包含系统生成的 OID 字段。在现代 PostgreSQL 中,普通表通常不再自动分配 OID。
HEAP_XMAX_KEYSHR_LOCK 表示元组被键共享锁(Key Share Lock)锁定。允许读取但防止冲突的 UPDATE/DELETE 操作。
HEAP_COMBOCID 表示使用组合命令 ID(Combined Command ID),用于优化子事务中的命令标识。
HEAP_XMAX_EXCL_LOCK 表示元组被排他锁(Exclusive Lock)锁定。阻止其他事务以任何方式访问该元组。
HEAP_XMAX_LOCK_ONLY 表示 t_xmax 仅用于记录锁持有者,而不是删除/更新操作。事务结束后锁会被释放。
HEAP_XMIN_COMMITTED 表示插入事务(t_xmin)已提交。这是一个缓存优化,避免频繁查询事务日志。
HEAP_XMIN_INVALID 表示插入事务(t_xmin)无效。通常意味着元组本身无效。
HEAP_XMAX_COMMITTED 表示删除/锁定事务(t_xmax)已提交。这是一个缓存优化。
HEAP_XMAX_INVALID 表示删除/锁定事务(t_xmax)无效。意味着 t_xmax 字段应该被忽略。
HEAP_XMAX_IS_MULTI 表示 t_xmax 是多事务 ID(MultiXactId),不是普通事务 ID。用于多个事务对同一元组持有锁的情况。
HEAP_UPDATED 表示元组是 UPDATE 操作的结果(新版本)。用于区分插入的元组和更新产生的元组。
HEAP_MOVED_OFF 表示元组已被 pre-9.0 VACUUM FULL 移出。保留用于二进制升级支持。
HEAP_MOVED_IN 表示元组已被 pre-9.0 VACUUM FULL 移入。保留用于二进制升级支持。

组合标志宏

宏名称 详细解释
HEAP_XMAX_SHR_LOCK 表示元组被共享锁锁定。这是 HEAP_XMAX_EXCL_LOCK 和 HEAP_XMAX_KEYSHR_LOCK 的组合。
HEAP_LOCK_MASK 用于检查任何类型的锁(共享锁、排他锁或键共享锁)的位掩码。
HEAP_XMIN_FROZEN 表示元组已被"冻结",对所有事务可见。这是防止事务 ID 回绕的关键机制。
HEAP_MOVED 表示元组已被移动(无论是移出还是移入)。保留用于二进制升级支持。
HEAP_XACT_MASK 用于提取所有与事务可见性相关的位的掩码(排除前4个与元组结构相关的位)。