zoukankan      html  css  js  c++  java
  • 如何从零学习PostgreSQL Page结构

    关注我们,获得更多资源

    640?wx_fmt=jpeg

    作者 | 李亮,云和恩墨西区交付工程师,长期服务于运营商、社保、银行、医院、公积金等行业,擅长数据库备份恢复,升级迁移,性能优化,sql优化。


    导读:PostgreSQL 号称是“世界上最先进的开源数据库”(The world's most advanced open source database),在DB-Engines的排名中长期处于第四的位置,并且增长趋势明显。开源的优势就是可以直接阅读源码,本文通过源码结合pageinspect对pg的page结构进行解析和学习。


    一、Page


    pg中的page和Oracle中的数据块一样,指的是数据库的块,操作系统块的整数倍个,默认是8K也就是两个操作系统块(4k的文件系统块)。这个大小在pg编译安装configure的时候通过--with-blocksize参数指定,单位是Kb。


    二、Page的内部结构


    2.1 page结构


    640?wx_fmt=png


    2.2 PageHeaderData数据结构 (页头)


    可以看到一个Page有 Pager header(页头),后面是linp(行指针),pd_lower和pd_upper分别是空闲空间的开始位置和结束位置;后面就是行数据(pg里面的行就是tuple)和special空间。整个page的结构比Oracle的数据块结构简单多了。


    typedef struct PageHeaderData

    {

        /* XXX LSN is member of *any* block, not only page-organized ones */

        PageXLogRecPtr pd_lsn;      /* LSN: next byte after last byte of xlog

                                     * record for last change to this page */

        uint16      pd_checksum;    /* checksum */

        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[FLEXIBLE_ARRAY_MEMBER]; /* line pointer array */

    } PageHeaderData;

     

    具体的长度和描述也都有详细说明:

    Field

    Type

    Length

    Description

    pd_lsn

    PageXLogRecPtr

    8 bytes

    LSN: next byte after last byte of WAL record for last change to this page

    pd_checksum

    uint16

    2 bytes

    Page checksum

    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

    简单来说,pd_lsn是指最后修改过这个page的lsn(log sequence number),这个和wal(write ahead log,同oracle redo)中记录的lsn一致。数据落盘时redo必须先刷到wal,这个pd_lsn就记录了最后data落盘时的相关redo的lsn。


    pd_checksum是校验和,在initdb初始化实例的时候通过-k参数指定开启,默认是关闭的,initdb之后不能修改,它基于FNV-1a hash算法,做了相应的更改。这个校验和与Oracle的checksum一样用于数据块在读入和写出内存时的校验。比如我们在内存中修改了一个数据块,写入到磁盘的时候,在内存里面先计算好checksum,数据块写完后再计算一遍cheksum是否和之前在内存中的一致,确保整个写出过程没有出错,保护数据结构不被破坏。


    pd_flags有以下的值:

    /*

     * pd_flags contains the following flag bits.  Undefined bits are initialized

     * to zero and may be used in the future.

     *

     * PD_HAS_FREE_LINES is set if there are any LP_UNUSED line pointers before

     * pd_lower.  This should be considered a hint rather than the truth, since

     * changes to it are not WAL-logged.

     *

     * PD_PAGE_FULL is set if an UPDATE doesn't find enough free space in the

     * page for its new tuple version; this suggests that a prune is needed.

     * Again, this is just a hint.

     */

    #define PD_HAS_FREE_LINES   0x0001  /* are there any unused line pointers? */

    #define PD_PAGE_FULL        0x0002  /* not enough free space for new tuple? */

    #define PD_ALL_VISIBLE      0x0004  /* all tuples on page are visible to

                                         * everyone */

     

    #define PD_VALID_FLAG_BITS  0x0007  /* OR of all valid pd_flags bits */

     


    pd_lower和pd_upper分别表示空闲空间起始位置和结束位置;pd_special在索引page才有效;pd_pagesize_version是page大小和page version的存储位,在不同数据库版本中,page version不一样:


    数据库版本

    pd_pagesize_version



    <7.3

    0



    7.3 & 7.4

    1



    8.0

    2



    8.1

    3



    >8.3

    4



    prune_xid表示这个page上最早删除或者修改tuple的事务id,在vacuum操作的时候会用到。(pg没有undo,旧的数据也在page中,用vacuum来清理)


    2.3 linp结构(行指针)


    640?wx_fmt=png


    lp_off是tuple的开始的偏移量;lp_flags是标志位;lp_len记录了tuple的长度。

    Field

    Length

    Description

    lp_off

    15 bits

    offset to tuple

    lp_flags

    2 bits

    State of iteam pointer

    lp_len

    15 bits

    Byte length of tuple


    2.4 tuple header结构(行头)

    typedef struct HeapTupleFields

    {

        TransactionId t_xmin;       /* inserting xact ID */

        TransactionId t_xmax;       /* deleting or locking xact ID */

        union

        {

            CommandId   t_cid;      /* inserting or deleting command ID, or both */

            TransactionId t_xvac;   /* old-style VACUUM FULL xact ID */

        }           t_field3;

    } HeapTupleFields;

     

    typedef struct DatumTupleFields

    {

        int32       datum_len_;     /* varlena header (do not touch directly!) */

        int32       datum_typmod;   /* -1, or identifier of a record type */

        Oid         datum_typeid;   /* composite type OID, or RECORDOID */

     

        /*

         * Note: field ordering is chosen with thought that Oid might someday

         * widen to 64 bits.

         */

    } DatumTupleFields;

     

    struct HeapTupleHeaderData

    {

        union

        {

            HeapTupleFields t_heap;

            DatumTupleFields t_datum;

        }           t_choice;

     

        ItemPointerData t_ctid;     /* current TID of this or newer tuple (or a

                                     * speculative insertion token) */

        /* Fields below here must match MinimalTupleData! */

        uint16      t_infomask2;    /* number of attributes + various flags */

        uint16      t_infomask;     /* various flag bits, see below */

        uint8       t_hoff;         /* sizeof header incl. bitmap, padding */

        /* ^ - 23 bytes - ^ */

        bits8       t_bits[FLEXIBLE_ARRAY_MEMBER];  /* bitmap of NULLs */

        /* MORE DATA FOLLOWS AT END OF STRUCT */

    };

    (*这部分代码在src/include/access/htup_details.h)


    也有对应的长度和描述的相详细说明:

    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

    uint16

    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


    union是共享结构体,起作用的变量是最后一次赋值的成员。来看看tuple header的结构。


    在HeapTupleFields中,t_xmin是插入这行tuple的事务id;t_xmax是删除或者锁住tuple的事务id;union结构中的t_cid是删除或者插入这个tuple的命令id,也就是命令序号;t_xvac是以前格式的vacuum full用到的事务id。


    在DatumTupleFields中,datum_len_ 指tuple的长度;datum_typmod是记录的type;datum_typeid是记录的id。


    页头HeapTupleHeaderData包含了union结构体中的两个变量HeapTupleFields和DatumTupleFields。t_ctid是tuple id,类似oracle的rowid,形式为(块号,行号)。


    t_infomask2 表示属性和标志位

    t_infomask 是flag标志位,具体值如下:

    /*

     * information stored in t_infomask:

     */

    #define HEAP_HASNULL            0x0001  /* has null attribute(s) */

    #define HEAP_HASVARWIDTH        0x0002  /* has variable-width attribute(s) */

    #define HEAP_HASEXTERNAL        0x0004  /* has external stored attribute(s) */

    #define HEAP_HASOID             0x0008  /* has an object-id field */

    #define HEAP_XMAX_KEYSHR_LOCK   0x0010  /* xmax is a key-shared locker */

    #define HEAP_COMBOCID           0x0020  /* t_cid is a combo cid */

    #define HEAP_XMAX_EXCL_LOCK     0x0040  /* xmax is exclusive locker */

    #define HEAP_XMAX_LOCK_ONLY     0x0080  /* xmax, if valid, is only a locker */

     

     /* 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  /* t_xmin committed */

    #define HEAP_XMIN_INVALID       0x0200  /* t_xmin invalid/aborted */

    #define HEAP_XMIN_FROZEN        (HEAP_XMIN_COMMITTED|HEAP_XMIN_INVALID)

    #define HEAP_XMAX_COMMITTED     0x0400  /* t_xmax committed */

    #define HEAP_XMAX_INVALID       0x0800  /* t_xmax invalid/aborted */

    #define HEAP_XMAX_IS_MULTI      0x1000  /* t_xmax is a MultiXactId */

    #define HEAP_UPDATED            0x2000  /* this is UPDATEd version of row */

    #define HEAP_MOVED_OFF          0x4000  /* moved to another place by pre-9.0

                                             * VACUUM FULL; kept for binary

                                             * upgrade support */

    #define HEAP_MOVED_IN           0x8000  /* moved from another place by pre-9.0

                                             * VACUUM FULL; kept for binary

                                             * upgrade support */

    #define HEAP_MOVED (HEAP_MOVED_OFF | HEAP_MOVED_IN)

     

    #define HEAP_XACT_MASK          0xFFF0  /* visibility-related bits */

    t_hoff表示tuple header的长度

    t_bits记录了tuple中null值的列


    三、实验


    3.1 安装pageinspect


    它在源码的crontrib目录下面

    postgres@cs-> cd postgresql-10.4/contrib/pageinspect

     

    make && make install

    postgres@cs-> make

    postgres@cs-> make install

     

    create extension就好了

    postgres@cs-> psql

    psql (10.4)

    Type "help" for help.

     

    postgres=# CREATE EXTENSION pageinspect;

    CREATE EXTENSION

     

    postgres=# x

    Expanded display is on.

    postgres=# dx

    List of installed extensions

    -[ RECORD 1 ]------------------------------------------------------

    Name        | pageinspect

    Version     | 1.6

    Schema      | public

    Description | inspect the contents of database pages at a low level

    -[ RECORD 2 ]------------------------------------------------------

    Name        | plpgsql

    Version     | 1.0

    Schema      | pg_catalog

    Description | PL/pgSQL procedural language

     

    3.2 创建建测试表t1,插入数据

    640?wx_fmt=png


    这里可以看到1000行数据用了6个数据块来存储(这里数据块从0开始),第6个数据块包含了73条记录(tuple)

    3.3 Pageinspect查看page


    这里我们通过两个函数来查看

    page_header 可以看到页头的数据

    heap_pageitems 可以看到具体tuple的数据


    3.3.1 page_header

    postgres=# xExpanded display is on.postgres=# select * from page_header(get_raw_page('t1',0));-[ RECORD 1 ]--------lsn       | 0/1671188checksum  | 0flags     | 0lower     | 772upper     | 784special   | 8192pagesize  | 8192version   | 4prune_xid | 0postgres=#

    可以看到第0个page的pd_lsn为0/1671188,checksum和flags都是0,这里没有开启checksum;tuple开始偏移是772(pd_lower),结束偏移是784(pd_upper),这个page是个表,所以它没有special,我们看到的sepcial就是8192了;pagesize是8192就是8K,version是4,没有需要清理的tuple,所以存储需要清理的tuple的最早事务的id就是0(prune_xid)。


    3.3.2 heap_page_items


    640?wx_fmt=png


    我们来看一行记录,可以看到它是第1行记录(lp=1),tuple的开始偏移量8160(lp_off),tuple的长度是32 bytes(lp_len为32,这个tuple是第一个插入的tuple,所以lp_off+lp_len=8160+32=8192),这行记录的插入事务id是557(t_min),和tuple的删除事务id是0(tmax),这里数据没有被删除,所以都是0。我们还可以看到t_ctid是(0,1),这里表示这个tuple是这个page中第一个块的第一条tuple;tinfomask2是2,t_infomask为2306,十六进制就是 0x0902 ,这个我们可以根据上面提到的值去看看具体的含义,0x0902 = 0x0100 + 0x0800 +0x0002;tuple头部结构(行头)的长度是24(t_hoff),t_data就是16进制存储的真正的数据了。


    3.4 删除一行数据观察prune_xid

    640?wx_fmt=png


    我们删除一行tuple可以看到prune_xid有了值,为559,这个559就是删除这个tuple的事务id(当前最早的删除或更改了tuple的事务id)


    640?wx_fmt=png


    同样,我们可以看到lp为1的这个tuple的t_xmax为559,这里就是删除这行tuple的事务id。

     

    PostgreSQL Page的物理结构相比Oracle的数据块来说简单很多了,源代码开放也便于学习和研究,pg是个很好很强大的数据库,值得好好学习。


    原创:李亮


    资源下载

    关注公众号:数据和云(OraNews)回复关键字获取

    2018DTCC , 数据库大会PPT

    2018DTC,2018 DTC 大会 PPT

    DBALIFE ,“DBA 的一天”海报

    DBA04 ,DBA 手记4 电子书

    122ARCH ,Oracle 12.2体系结构图

    2018OOW ,Oracle OpenWorld 资料

    PRELECTION ,大讲堂讲师课程资料

    近期文章

    企业数据架构的云化智能重构和变革(含大会PPT)

    Oracle研发总裁Thomas Kurian加盟Google Cloud

    变与不变: Undo构造一致性读的例外情况

    Oracle 18c新特性:动态 Container Map 增强 Application Container 灵活性

    Oracle 18c新特性:Schema-Only 帐号提升应用管理安全性

    Oracle 18c新特性:多租户舰队 CDB Fleet (含PPT)

    为什么看了那么多灾难,还是过不好备份这一关?

    640?wx_fmt=jpeg

  • 相关阅读:
    [LeetCode] Excel Sheet Column Title
    [LeetCode] Paint House
    [LeetCode] Interleaving String
    [LeetCode] Plus One
    [LeetCode] Spiral Matrix
    [LeetCode] Spiral Matrix II
    [LeetCode] Rotate Image
    [LeetCode] Maximum Gap
    android学习日记13--数据存储之ContentProvide
    android学习日记0--开发需要掌握的技能
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13312215.html
Copyright © 2011-2022 走看看