美文网首页pg
第 13 课 PostgreSQL 存储之Page(页面)源码分

第 13 课 PostgreSQL 存储之Page(页面)源码分

作者: 椟夜 | 来源:发表于2018-10-18 14:01 被阅读28次

    在文章:PostgreSQL 数据存储结构 中我们介绍了控制页和数据页的基本存储结构,那是从物理上进行说明各种页面的用途。

    下面我们是从代码逻辑上来分析页面是如何进行操作和控制的。

    页面布局示意图

    image.png

    PageHeader

    先简单的看一下源代码中定义的Page头部信息结构体,中文是我自己的理解:
    源码位置:/src/include/storage/bufpage.h

    typedef struct PageHeaderData
    {
        /* XXX LSN is member of *any* block, not only page-organized ones */
        /* 
           日志文件信息,保存了该页面最后一次被修改的操作对应到确切的日志文件
           位置,包括了日志文件ID和日志文件偏移量
        */
        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 */
        /* 特殊用途空间的起始位置,结束位置是page尾部,直到页面结束 */
        LocationIndex pd_special;   /* offset to start of special space */
        uint16      pd_pagesize_version;
        /* 页面类型: 有多种控制页面类型和数据页面 */
        uint8       pd_type;
        /* 物理存储与逻辑存储的关联对象的OID */
        Oid         pd_oid;         /* oid of related object,
                                     * pg_class.oid for IAM, pg_class.relfilenode for others */
        /* 数据开始位置 */
        ItemIdData  pd_linp[1];     /* beginning of line pointer array */
    } PageHeaderData;
    

    header用来保存该page相关的数据。

    • pd_lsn、pd_tli 记录日志相关的信息。

    • pd_flags 表示页面状态
      PD_ALL_VISIBLE(0x0001) - 表示所有元组可以访问。
      PD_VALID_FLAG_BITS(0x0003) - 加密存储相关。
      PG_PAGE_ENCRYPTED(0x0002) - 支持统一存储加密引擎。

    • pd_lower
      空闲空间起始位置,随着插入和删除操作位置发生变化。初始化时就是pd_linp的偏移位置。

    • pd_upper
      空闲空间结束位置,随着插入和删除操作位置发生变化。初始化时与pd_special相同。

    • pd_special
      相当于画了一条线,从pd_special这个位置到page的结尾,都是special的地盘,普通插入Tuple,都不许进入这个私有地盘。而且这个pd_special一旦初始化之后,这个值就不会动了。
      主要用于加密存储是保存加密相关的数据。

    • pd_pagesize_version
      高位字节表示page大小。
      低位字节表示版本号(PG_PAGE_LAYOUT_VERSION),代码写死。
      宏PageSetPageSizeAndVersion用于设置大小和版本。
      宏PageGetPageSize()获取Page大小。
      宏PageGetPageLayoutVersion()获取版本号。
      对应的实现:

    #define PageSetPageSizeAndVersion(page, size, version) \
    ( \
        AssertMacro(((size) & 0xFF00) == (size)), \
        AssertMacro(((version) & 0x00FF) == (version)), \
        ((PageHeader) (page))->pd_pagesize_version = (size) | (version) \
    )
    #define PageGetPageSize(page) \
        ((Size) (((PageHeader) (page))->pd_pagesize_version & (uint16) 0xFF00))
    #define PageGetPageLayoutVersion(page) \
        (((PageHeader) (page))->pd_pagesize_version & 0x00FF)
    
    • pd_type
      有这些类型:P_GAM、P_IAM、P_PFS、P_DCM、P_BCM、P_SGAM、P_VM。
      对应的定义:
    typedef enum PageType{
        P_TEMPDATA,
        P_HEAP,
        P_BTREE,
        P_HASH,
        P_GIN,
        P_GIST,
        P_SEQUENCE,
        P_GAM,
        P_PFS,
        P_SGAM,
        P_BCM,
        P_DCM,
        P_IAM,
        P_VM, /* vm page*/
        P_UNKNOWN,
    }PageType;
    

    Page初始化

    通过如下函数初始化页面:

    void
    PageInit(Page page, Size pageSize, Size specialSize, PageType type)
    {
        PageHeader  p = (PageHeader) page;
        uint16      pd_flags = p->pd_flags;
    
        specialSize = MAXALIGN_DISK(specialSize);
    
        Assert(pageSize == BLCKSZ);
        Assert(pageSize > specialSize + SizeOfPageHeaderData);
    
        /* Make sure all fields of page are zero, as well as unused space */
        MemSet(p, 0, pageSize);
    
        /* p->pd_flags = 0;                     done by above MemSet */
        PageSetPageSizeAndVersion(page, pageSize, PG_PAGE_LAYOUT_VERSION);
        PageSetPageType(page, type);
        PageSetOid(page, InvalidOid); /* we will set it later for heap page */
        /* Support the uniform store encryption engine. */
        p->pd_lower = SizeOfPageHeaderData;
        if ((pd_flags & PG_PAGE_ENCRYPTED) && !PageIsCtrlPage(page))
            p->pd_special = pageSize - specialSize -
                            G_EncryptData->block_key_num * (G_EncryptData->key_len + G_EncryptData->verify_len);
        else
            p->pd_special = pageSize - specialSize;
        p->pd_upper = p->pd_special;
    
        /*
         * Empty page is all visible. We must set PD_ALL_VISIBLE because 
         * recycled page's "visibility map bit" may be set before. (Recycled page -- 
         * means page was deallocated by Vacuum or something else, and then 
         * be allocated.)
         */
        PageSetAllVisible(p);
        if ((pd_flags & PG_PAGE_ENCRYPTED) && !PageIsCtrlPage(page))
            PageSetEncrypted(p);
    }
    

    待分析...

    Page有效性检查

    通过如下函数实现:

    bool
    PageHeaderIsValid(PageHeader page)
    {
        char       *pagebytes;
        int         i;
    
        /* Check normal case */
        /* Support the uniform store encryption engine. */
        if (PageGetPageSize(page) == BLCKSZ &&
            PageGetPageLayoutVersion(page) == PG_PAGE_LAYOUT_VERSION &&
            (page->pd_flags & ~PD_VALID_FLAG_BITS) == 0 &&
            page->pd_lower >= SizeOfPageHeaderData &&
            page->pd_lower <= page->pd_upper &&
            page->pd_upper <= page->pd_special &&
            (PageIsEncrypted(page) ?
                page->pd_special <= BLCKSZ - G_EncryptData->block_key_num * (G_EncryptData->key_len + G_EncryptData->verify_len) :
                page->pd_special <= BLCKSZ) &&
            page->pd_special == MAXALIGN_DISK(page->pd_special))
            return true;
    
        /* Check all-zeroes case */
        pagebytes = (char *) page;
        for (i = 0; i < BLCKSZ; i++)
        {
            /* Support the uniform store encryption engine. */
            if (pagebytes[i] != 0 && pagebytes[i] != 2)
                return false;
        }
        
        return true;
    }
    

    待分析...

    Insert操作分析

    当初始化的时候,pd_lower设置为SizeOfPageHeaderData,pd_upper设置为和pd_special一样。但是注意,这个lower和upper不是固定的,随着Tuple的不断插入,lower变大,而upper不断变小。当我们每插入一条Tuple,需要在当前的lower位置再分配一个Item,记录Tuple的长度,Tuple的起始位置offset,还有flag信息。这个Page Header中的pd_lower就是记录分配下一个Item的起始位置。所以如果不断插入,lower不断增加,每增加一条Tuple,就要分配一个Item(4个字节)

    对应实现函数:

    • PageAddItem
    OffsetNumber
    PageAddItem(Page page,
                Item item,
                Size size,
                OffsetNumber offsetNumber,
                Size tupleSize,
                ItemIdFlags flags)
    

    待分析...

    Delete操作分析

    对应实现函数:

    • PageIndexMultiDelete
    void
    PageIndexMultiDelete(Page page, OffsetNumber *itemnos, int nitems)
    

    offnum指示第几个记录,offnum是从1开始计数的,查找对应item 是offnum-1.
    我们找到Item,就可以找到Tuple对应的offset和size。
    待分析...

    发现更多宝藏

    我在喜马拉雅上分享声音

    《PostgreSQL数据库内核分析》,点开链接可以听听,有点意思。

    《数据库系统概论(第4版)》,点开链接可以听听,有点意思。

    更多IT有声课程,点我发现更多

    第 0 课 PostgreSQL 系列文章列表

    其他相关文章分享列表:

    第 23 课 PostgreSQL 创建自己的数据库、模式、用户
    第 22 课 PostgreSQL 控制文件
    第 21 课 PostgreSQL 日志系统
    第 16 课 查询过程源码分析
    第 15 课 PostgreSQL 系统参数配置
    第 14 课 PostgreSQL 数据存储结构
    第 13 课 PostgreSQL 存储之Page(页面)源码分析
    第 12 课 PostgreSQL 认证方式
    第 11 课 PostgreSQL 增加一个内核C函数
    第 10 课 PostgreSQL 在内核增加一个配置参数
    第 09 课 PostgreSQL 4种进程启动方式
    第 08 课 PostgreSQL 事务介绍
    第 07 课 PostgreSQL 数据库、模式、表、空间、用户间的关系
    第 06 课 PostgreSQL 系统表介绍
    第 05 课 PostgreSQL 编译源代码进行开发
    第 04 课 PostgreSQL 安装最新的版本
    第 03 课 PostgreSQL 代码结构
    第 02 课 PostgreSQL 的特性、应用、安装
    第 01 课 PostgreSQL 简介及发展历程

    上面文章都在专辑中:PostgreSQL专辑链接,点我查看

    如果有用,可以收藏这篇文件,随时在更新....

    更多交流加群: PostgreSQL内核开发群 876673220

    亲,记得点赞、留言、打赏额!!!

    上一课
    下一课

    相关文章

      网友评论

        本文标题:第 13 课 PostgreSQL 存储之Page(页面)源码分

        本文链接:https://www.haomeiwen.com/subject/ymojzftx.html