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

在文章: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

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

上一课
下一课

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容

  • 本文简单介绍了PG数据页Page中存储的原始内容以及如何阅读它们,包括页头PageHeader和行数据指针Item...
    EthanHe阅读 1,885评论 4 2
  • 本文简单介绍了PG数据表的存储基础知识以及可用于解析数据页Page内容的pageinspcet插件。 一、PG数...
    EthanHe阅读 4,845评论 4 4
  • 前情回顾 三 更 我跟海哥住在一起,我们住的是省重点高中旁的学区别墅,海哥可不差钱。想一想,盗战国墓,什么青铜六山...
    莹Innsane阅读 274评论 1 1
  • 三月份那时在找工作,无意间搜公众号看文章,看了一篇何沐芝老师的文章,现在记不得是哪一篇文章了,进而加了微信,令我意...
    廖阿凡阅读 169评论 0 2
  • 昨晚南科大实验二小的刘安老师给大家分享了信息技术提升班第四课《网络技能及学习软件》。刘老师从下面几个方面为...
    宁都谢志雄阅读 869评论 7 11