暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

PostgreSQL内核解析:PostgreSQL深入解析heap表物理结构




本文以 psql (PostgreSQL) 15 为分析对象,解析heap表物理结构,在pg数据库中 heap表数据文件以 pages为单位划分为定长的单元,默认为8192 byte (8 KB),pages编号0开始递增,pages的编号称为block number,pages中被数据填充满后,pg通过在文件的末尾增加新的page,扩展数据文件的大小。

1

小结
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进制显示,可以看到<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]$

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语言位域结构说明

<font color=red size=5>ItemIdData</font>结构为C语言的位域结构体,该结构的定义从 <font color=red size=5>低位从到高位</font>,即结构体中的 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

遗留问题
对本节涉及的未展开详细解析的问题,后续单独一节进行分析
1、《pg页头信息查询-pageinspect扩展用法讲解》 后面单独一节详细讲解
2、《深入解析pg lsn》后面单独一节详细讲解
3、《checksum与pd_tli问题分析》后面单独一节详细讲解
4、《pg数据文件page扩展规律》,是否每次只扩展一个8K pages(默认),后面单独一节详细讲解
5、《深入解析Tuple物理结构》后面单独一节详细讲解
10

参考资料
10.1、源码信息
 
说明

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进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论