1_PostgreSQL深入解析heap表物理结构
1、内容概述
本文以psql (PostgreSQL) 15beta1 为分析对象,解析heap表物理结构,在pg数据库中 heap表 数据文件以 pages为单位划分为定长的单元,默认为8192 byte (8 KB),pages编号0开始递增,pages的编号 称为block number,pages中被数据填充满后,pg通过在文件的末尾增加新的page,扩展数据文件的大小。
小结:
1、pg中的pages又称为block,类似Oracle中的blocks,Oracke中的block默认大小也为8K.
2、把datafile中的物理存储单位称为pages,后面的pages编号称为block number,概念存在不连贯性,可能由于历史原因有不同的概念定义,block 和后面的block number连贯性更好点,便于理解。
3、page扩展时是一次扩展一个page还是一次扩展多个,需要后面实验验证,Oracle中是以extent进行扩展,扩展规则如下所示:
BLOCKS COUNT(1) ---------- ---------- 8 16 128 63 1024 120 8192 ??? --这个阀值后面没有再测试
2、创建测试表
[postgres@pg ~]$ psql psql (15beta1) Type "help" for help. postgres=# create table test(a1 char(10)); CREATE TABLE postgres=# SELECT pg_relation_filepath('test'); pg_relation_filepath ---------------------- base/5/32793 (1 row) postgres=#
3、当Tuples=0(0行数据)时
[postgres@pg 5]$ ls -ltr|grep 32793 -rw------- 1 postgres dba 0 Jun 20 11:26 32793 [postgres@pg 5]$ dd if=/u01/pg15/pgdata/base/5/32793 bs=8192 skip=0 count=1 | od -t x1 0+0 records in 0+0 records out 0 bytes (0 B) copied, 2.5415e-05 s, 0.0 kB/s 0000000 [postgres@pg 5]$
小结:
1、pg在创建heap表后会创建一个大小为0 byte的空文件,没有初始化任何块及段头块信息。
2、Oracle表默认在创建后会初始化第一个extent,大小为8个blocks,前3个块为位图块,后面5个块为未初始化的数据块。
4、当Tuples=1(1行数据)时
4.1、插入第1行数据库
postgres=# insert into test values(1); INSERT 0 1 postgres=# [postgres@pg 5]$ ls -ltr|grep 32793 -rw------- 1 postgres dba 8192 Jun 20 12:00 32793 [postgres@pg 5]$
4.2、以16进程方式读取块信息 dd+od VS dd+hexdump
以dd+od方式读取二进制文件的块信息,并以16进制显示,可以看到左边的行号显示有误。

以dd+hexdump方式读取二进制文件的块信息,并以16进制显示,左边的行号显示正常。 这里的 0x2000(16进制) = 8192(10进制),8192为一个pg page的默认大小。

小结:
1、将插入第一行数据,pg数据文件从0 byte初始化为8192 byte。
2、对于二进制文件内容的读取常用的方式有 dd+od和 dd+hexdump二种方式,dd+od的方式左边的行号显示有误,容易产生误解,我们后面均采用dd+hexdump的方式。
3、在C语言中以 0x或0X表示16进制,10进制前没有前缀符号
4、信息系统中的不 同进制表示法,还有以下标法进行区分,以 8192为例:
二进制:0010 0000 0000 00002
十进制:819210
十六进制:200016
4.3、安装pageinspect扩展查看页头信息
su - postgres cd /soft/postgresql-15beta1/contrib/pageinspect [postgres@pg pageinspect]$ make && make install postgres=# create extension pageinspect; CREATE EXTENSION postgres=# [postgres@pg pageinspect]$ psql psql (15beta1) Type "help" for help. postgres=# create extension pageinspect; CREATE EXTENSION postgres=# SELECT * FROM page_header(get_raw_page('test', 0)); lsn | checksum | flags | lower | upper | special | pagesize | version | prune_xid ------------+----------+-------+-------+-------+---------+----------+---------+----------- 1/868D38F0 | 0 | 0 | 28 | 8152 | 8192 | 8192 | 4 | 0 (1 row) postgres=#
小结:
查看PageHeader信息前需要安装 pageinspect 扩展,详细用法请参见以下文档:
https://www.postgresql.org/docs/15/pageinspect.html
《pg pageinspect扩展用法讲解》 后面单独一节详细讲解
《深入解析pg lsn》后面单独一节详细讲解
4.4、解析pg page物理结构
[postgres@pg 5]$ dd if=/u01/pg15/pgdata/base/5/32793 bs=8192 skip=0 count=1 | hexdump -C 1+0 records in 1+0 records out 8192 bytes (8.2 kB) copied, 4.712e-05 s, 174 MB/s 00000000 01 00 00 00 f0 38 8d 86 00 00 00 00 1c 00 d8 1f |.....8..........| 00000010 00 20 04 20 00 00 00 00 d8 9f 46 00 00 00 00 00 |. . ......F.....| 00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00001fd0 00 00 00 00 00 00 00 00 03 03 00 00 00 00 00 00 |................| 00001fe0 00 00 00 00 00 00 00 00 01 00 01 00 02 08 18 00 |................| 00001ff0 17 31 20 20 20 20 20 20 20 20 20 00 00 00 00 00 |.1 .....| 00002000 [postgres@pg 5]$
4.5、图解PageHeader物理结构

小结:
1、char(10)的一个Tuple占用长度10 bytes,《深入解析Tuple物理结构》单独一节详解
5、当Tuples=2(2行数据)时
5.1、插入第2行数据库
postgres=# insert into test values(1); INSERT 0 1 postgres=# [postgres@pg 5]$ ls -ltr|grep 32793 -rw------- 1 postgres dba 8192 Jun 20 12:00 32793 [postgres@pg 5]$
5.2、解析pg page物理结构
[postgres@pg 5]$ dd if=/u01/pg15/pgdata/base/5/32793 bs=8192 skip=0 count=1 | hexdump -C 1+0 records in 1+0 records out 8192 bytes (8.2 kB) copied, 6.1767e-05 s, 133 MB/s 00000000 01 00 00 00 a8 f3 92 86 00 00 00 00 20 00 b0 1f |............ ...| 00000010 00 20 04 20 00 00 00 00 d8 9f 46 00 b0 9f 46 00 |. . ......F...F.| 00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 00001fb0 05 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00001fc0 02 00 01 00 02 09 18 00 17 31 20 20 20 20 20 20 |.........1 | 00001fd0 20 20 20 00 00 00 00 00 03 03 00 00 00 00 00 00 | .............| 00001fe0 00 00 00 00 00 00 00 00 01 00 01 00 02 09 18 00 |................| 00001ff0 17 31 20 20 20 20 20 20 20 20 20 00 00 00 00 00 |.1 .....| 00002000 [postgres@pg 5]$
5.3、解析line pointer信息

6、line pointer结构解析问题
6.1、PageHeaderData结构源码
typedef struct PageHeaderData
{
/* XXX LSN is member of *any* block, not only page-organized ones */
XLogRecPtr pd_lsn; /* LSN: next byte after last byte of xlog
* record for last change to this page */
uint16 pd_tli; /* least significant bits of the TimeLineID
* containing the LSN */
uint16 pd_flags; /* flag bits, see below */
LocationIndex pd_lower; /* offset to start of free space */
LocationIndex pd_upper; /* offset to end of free space */
LocationIndex pd_special; /* offset to start of special space */
uint16 pd_pagesize_version;
TransactionId pd_prune_xid; /* oldest prunable XID, or zero if none */
ItemIdData pd_linp[1]; /* beginning of line pointer array */
} PageHeaderData;
typedef PageHeaderData *PageHeader;
6.2、ItemIdData结构源码
/*
* An item pointer (also called line pointer) on a buffer page
*
* In some cases an item pointer is "in use" but does not have any associated
* storage on the page. By convention, lp_len == 0 in every item pointer
* that does not have storage, independently of its lp_flags state.
*/
typedef struct ItemIdData
{
unsigned lp_off:15, /* offset to tuple (from start of page) */
lp_flags:2, /* state of item pointer, see below */
lp_len:15; /* byte length of tuple */
} ItemIdData;
typedef ItemIdData *ItemId;
6.3、pd_linp信息图解

6.4、C语言位域结构说明
ItemIdData结构为C语言的位域结构体,该结构的定义从 低位到高位,即结构体中的 lp_len->lp_flags->lp_off为从高位到低的的顺序。
7、尝试产生第二个page
当第一个page填满数据后,pg在数据文件末尾产生第二个8k大小的page,数据文件从源来的8K扩展为16K。
postgres=# insert into test select n from generate_series(1,230) as n; INSERT 0 230 postgres=# [postgres@pg 5]$ ls -ltr|grep 32793 -rw------- 1 postgres dba 24576 Jun 20 21:52 32793_fsm -rw------- 1 postgres dba 16384 Jun 20 21:52 32793 [postgres@pg 5]$
小结:
思考问题一、以上只测试了当第一个page满后,pg只扩展一个page到数据文件的末尾,随着数据文件的扩大是否一直每次只扩展一个page,留做一个问题,我们后面单独一节分析
8、关于 checksum 字段的问题
8.1、PageHeader源码结构
typedef struct PageHeaderData
{
/* XXX LSN is member of *any* block, not only page-organized ones */
XLogRecPtr pd_lsn; /* LSN: next byte after last byte of xlog
* record for last change to this page */
uint16 pd_tli; /* least significant bits of the TimeLineID
* containing the LSN */
uint16 pd_flags; /* flag bits, see below */
LocationIndex pd_lower; /* offset to start of free space */
LocationIndex pd_upper; /* offset to end of free space */
LocationIndex pd_special; /* offset to start of special space */
uint16 pd_pagesize_version;
TransactionId pd_prune_xid; /* oldest prunable XID, or zero if none */
ItemIdData pd_linp[1]; /* beginning of line pointer array */
} PageHeaderData;
typedef PageHeaderData *PageHeader;
8.2、查询页头信息
postgres=# SELECT * FROM page_header(get_raw_page('test', 0)); lsn | checksum | flags | lower | upper | special | pagesize | version | prune_xid ------------+----------+-------+-------+-------+---------+----------+---------+----------- 1/868D38F0 | 0 | 0 | 28 | 8152 | 8192 | 8192 | 4 | 0 (1 row) postgres=#
小结:
查询页头信息中 有 checksum字段,但在源码 PageHeaderData结构体中没有该字段对应位置为
uint16 pd_tli; /* least significant bits of the TimeLineID
《checksum与pd_tli问题分析》会做新单独一节进行分析
9、参考资料
9.1、源码信息
说明:
The structure PageHeaderData is defined in src/include/storage/bufpage.h.
9.2、blob信息
http://www.interdb.jp/pg/pgsql01.html





