PG数据库HOT链简介

举报
亚中风风寒 发表于 2021/04/20 15:49:43 2021/04/20
【摘要】 介绍数据库HOT机制,通过HOT机制解决数据库数据膨胀问题

在介绍数据库的HOT之前需要了解下PG的Page组织形式,Page页面起始位置是一个PageHeadData,就是存储本页面上最新更新的某个元组对应Xlog的LSN、页面空闲空间的起止地址以及页面的CRC等信息。其他的空间组织结构如下图所示,每条元组有一个头指针即linePointer,使用头指针指向具体的元组数据。为什么要做成这样的组织形式。第一,每条元组的大小不一样,从而需要一个标记元组长度的字节去存储每个元组的长度,所以必须有额外的空间去存储各自元组的长度,所占的空间跟随各自元组空间存储一块或者单独拎出来存储,其都是可以的。第二,为了提升查询的效率问题,可以将元组是死亡还可见的标记位信息单独存储,不用解析元组数据而判断元组可见性,从而可以将存储这些信息的空间整合到一块,即下图中linepointer数组。第三,将这些固定长度的信息整合到一块,读取linePointer信息更加高效,不用来回进行offset偏移。第四,这样组织结构,对于一条元组的ctid信息,只要找到还哪个page以及poage中是哪个linepointer,就最终可以找到元组的信息。因此,元组的ctid其就表示的是page编号以及page中linepointer的数组下标。

由于有了这样的页面组织形式,所以对于携带索引的表进行元组的插入,更新时才有了后面的HOT链这个东东。

页面结构.png

HOT链到底是什么,全称为Hot Update链,为什么要实现hot update,其主要是要解决索引膨胀的问题。

PG原有实现索引时如下图所示,插入一条元组,就会在索引页面上增加一个item(若索引中key值相等,则认为一个可以对应的所有的value组织为一个链表形式),若在非索引列上进行更新操作,则在此key对应的value链表上增加一个Item。如下图所示,更新操作后索引中需要增加一个索引item。若查询计划中走IndexScan时,则需要访问索引页面中的两个value,找到heap页面上的元组,然后通过快照判断元组可见性。这种索引实现方式的劣势最明显的是,索引严重膨胀,其次由于需要拿多个索引value,同时多个版本的元组都需要进行可见性判断无法进行后续的优化处理(hot链时对于多版本可以进行优化查询),导致查询性能低下。

未hotupdate之前.png


要降低索引文件的膨胀问题,那就必须在对数据元组更新时不更新索引文件。也就是说一个key需要对应多个版本的元组(即通过索引能够找到可见的元组和不可见的元组)。所以HOT链就诞生了,如下图所示,元组1为更新前的,元组2为更新后的新元组,从图中可以看到更新前后没有更新索引文件,是因为刚好在数据页面有linepointer,linepointer1指向旧元组,旧元组中若标记为HEAP_HOT_UPDATED,则说明更新前后的元组在同一个页面,同时可以通过访问元组1进而定位到元组2 的偏移量最终解析出元组2。对于元组1和元组2到底哪个可见,依然使用快照判断可见性。若执行计划中为seqScan,即没有走索引,则扫描此页面时,挨个linepointer进行读取,即读取元组2数据时是通过linepointer2访问的。

HOT.png

这样会的设计方式仍然有一个问题,就是说索引膨胀问题解决了,而数据文件反而膨胀了,我们会发现一个元组频繁的更新操作,将可能会在同一个页面上出现多个版本,而且多个版本元组之间的寻址是通过元组自身存储的信息定位的,这将导致老元组的空间无法回收或者重复使用,从而页面膨胀严重。因此为了解放旧元组,也就是将旧元组与新元组解耦,可以将此新旧元组之间的依赖关系转接为多个linepointer与新元组之间的关系,这样查找新元组就不需要依赖旧元组,此时若旧元组可以回收,即旧元组对谁来说都是不可见的,则可以先将旧元组进行prune,无论通过seqscan还是indexscan,都无法访问到旧元组,如下图所示,linepointer1与linepointer2建立一种对应关系,linepointer2可以通过记录的偏移量查找到元组2。因此当走indexscan时,首先从索引文件中查找到的value可以定位扫linepointer1,看到linepointer被标记为redirect,则直接访问linepointer2,从而直接访问元组2。当走seqscan时,发现linepointer1已经不是normal状态则直接访问下一个linpointer2,进而访问元组2。

prune后.png

这时候对于元组1存储在页面上是一种浪费,空间膨胀,但是有了上面的设计后,如果在页面上将元组1删除,则不会影响页面中其他任何元组的访问,因此这个动作则可以交给vacuum进程去做,如下图所示元组1从页面上删除,删除的操作是通过将有效元组向页面尾的位置移动实现的,类似于挤泡泡,将中间的一些泡泡挤掉,后面的泡泡向前移动。此时页面的空闲空间就会提升,其他元组进来后就可以进行存储。有一点需要注意的是这种解决页面空间膨胀的方法只是空间重复利用,并不是将空间真正的从磁盘上降低,因此页面整理前后的表大小是不会变的。若要从磁盘空间上减少空间,则可以通过vacuum full操作将不可见元组的空间从磁盘上真正回收。

碎片整理后.png









【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

举报
请填写举报理由
0/200