本文以 psql (PostgreSQL) 15
为分析对象,解析heap表物理结构,在pg数据库中 heap表数据文件以 pages为单位划分为定长的单元,默认为8192 byte (8
KB),pages编号0开始递增,pages的编号称为block
number,pages中被数据填充满后,pg通过在文件的末尾增加新的page,扩展数据文件的大小。
2、把datafile中的物理存储单位称为pages,后面的pages编号称为block number,概念存在不连贯性,可能由于历史原因有不同的概念定义,block 和后面的block number连贯性更好点,便于理解。
3、page扩展时是一次扩展一个page还是一次扩展多个,需要后面实验验证,Oracle中是以extent进行扩展,扩展规则如下所示:
BLOCKS COUNT(1)
---------- ----------
8 16
128 63
1024 120
8192 ???--这个阀值后面没有再测试
[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=#
[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个块为未初始化的数据块。
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]$
以dd+od方式读取二进制文件的块信息,并以16进制显示,可以看到<font color=red size=5>左边的行号显示有误。</font>

image-20221028224936659

以dd+hexdump方式读取二进制文件的块信息,并以16进制显示,<font color=red size=5>左边的行号显示正常。</font> 这里的 0x2000(16进制) = 8192(10进制),8192为一个pg page的默认大小。
image-20221028224958347
小结:
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 0000~2~
十进制:8192~10~
十六进制:2000~16~
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]$
小结:
1、char(10)的一个Tuple占用长度10 bytes,《深入解析Tuple物理结构》单独一节详解
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]$
``

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;
``
/*
* 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.4、C语言位域结构说明
<font color=red size=5>ItemIdData</font>结构为C语言的位域结构体,该结构的定义从 <font color=red size=5>低位从到高位</font>,即结构体中的 lp_len->lp_flags->lp_off为从高位到低的的顺序。
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,留做一个问题,我们后面单独一节分析
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;
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问题分析》会做新单独一节进行分析
1、《pg页头信息查询-pageinspect扩展用法讲解》 后面单独一节详细讲解
2、《深入解析pg lsn》后面单独一节详细讲解
3、《checksum与pd_tli问题分析》后面单独一节详细讲解
4、《pg数据文件page扩展规律》,是否每次只扩展一个8K pages(默认),后面单独一节详细讲解
5、《深入解析Tuple物理结构》后面单独一节详细讲解
说明
The structure PageHeaderData is defined in src/include/storage/bufpage.h.
10.2、blob信息
http://www.interdb.jp/pg/pgsql01.html


文章转载自开源软件联盟PostgreSQL分会,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




