Hash索引

一、两种类型的Hash表

  • Hash表:假设有编号为 0 到 B-1 的B个位置(桶)存放数据,使用一个Hash函数,把需要存储的数据作为参数计算一个介于 0 到 B-1的值,将这个数据存放到这个值对应的位置(桶),所有数据存放完毕,则形成一张Hash表

1.静态Hash表

桶数目B不变

2. 动态Hash表:

  桶数据允许改变,动态Hash表分为分为两种

2.1 可扩展Hash表

  • 为桶增加一个中间层,即使用一个指针数组来表示桶,而不是直接使用数据快作为桶。指针指向具体存储数据的数据块
  • 指针数组能增长,数组长度总是2的幂。每增长一次,指针数组长度翻倍
  • Hash函数为每个键值计算出一个足够长的二进制序列,使用二进制序列中从第一位开始的若干位作为键的Hash值,这个值对应的就是一个指针

2.2 线性Hash表

  • 桶数B由需要存储的记录数决定
  • 用于标识桶数的二进制位数为[log2B]
  • 桶的增长较为缓慢。
  • 如何插入元组:计算元组的Hash值,设这个值为K。假设此时[log2B]的值为i。将K转换为二进制数,从右开始取i位,得到的数即为元组要放入的桶数

二. postgresql中用做索引的Hash表为线性Hash表

  Hash表有四种不同类型的页面,分别为元页、桶页、溢出页、位图页:

 

 

 

1 元页:

  • 每个Hash索引都有一个元页。元页为索引的0号页,不属于任何桶。
  • 元页记录了Hash的版本号、Hash索引记录的索引元组数目以及桶和位图的信息。
  • 通过元页可以了解Hash索引总体使用情况,在索引的插入,溢出页的分配回收以及Hash表的扩展等过程中,都要用到元页

数据结构和各成员意义如下:

 

 

 2.桶页

  • Hash表有多个桶,每个桶由一个或多个页组成,每个桶的第一页称为桶页,其他页称为溢出页。
  • 桶页随着桶的建立而建立,如果有溢出页,则桶页结构中的hasho_nextblkno字段指向溢出页,形成链结构
  • 桶页的分配:splitpoint值修改时,将进行桶的分配,每次分配的数目都是2的幂次。0号和1号桶时Hash表初始化时候分配的,当splitpoint为2时,进行一次分配,将同时分配2号、3号桶页。当需要第5个桶时,splitpoint值为3,将分配4~7号桶页,依此类推。每次分配桶页时候,也会在桶页后按需求分配一定数量的溢出页,溢出页数量记录在hashm_spare数组中
  • 每次分配的桶页在磁盘上是连续存储的,因此,可以根据桶页数计算桶所对应的磁盘块号:

注:hashm_spare记录第i次分裂后溢出页个数和位图页个数的和

 

 

 3.溢出页

  • 当某个元组在它所属的桶中放不下时,就需要将其放在该桶的溢出页。溢出页和桶页之间使用双向链表连接。
  • 元页中使用数组hashm_spares记录每次桶的扩展时分配的溢出页数
  • 溢出页一旦分配,便一直存在,即使被回收也只是标记为空闲,并没有释放物理空间

4.位图

  当溢出页上的元组被移除时,就要将溢出页回收,回收溢出页并不是把它还给操作系统,而是继续由PostgreSQL管理,以便下一次需要时使用。因此需要一种机制来记录每一个溢出页是否可用。

  • 用于管理Hash索引溢出页和位图页本身的使用情况
  • 在位图中,对于每个溢出页和位图页都有一个比特位标识其使用情况。0表示该页可用,1表示该页不可用。位图页的格式由页头PageHeaderData,页尾Special Space和中间的位数组组成,中间的位数组的每一位对应一个溢出页或位图。位图中的位数必须为2的幂次

 

  •  通过元页中的hashm_mapp数组可以找到位图对应的块号
  • 利用元页中的hashm_mapp以及上面提到的hashm_spares可以快速查找到空闲溢出页

5.Hash表页面分配示例:

1.初始化

 

 

2.为0号桶分配溢出页0和溢出页1:

 

 3.增加一个桶

 

 4.为2号桶增加一个溢出页

 

 三.Hash索引的实现

1.Hash表的创建:

  1. 初始化索引的元页、桶以及位图页
  2. 调用扫描函数对待索引的表进行扫描,生成索引元组
  3. 将索引元组插入到Hash表

2.元组的插入:

读取元组的信息,计算该索引元组应插入的桶号,若该桶有足够的空间,则直接插入,否则申请溢出页。完成插入后,系统根据元页信息判断是否需要增加新的桶

 

 

3.溢出页的分配和回收

索引元组插入过程中如果桶中没有空间,就需要创建一个溢出页存放索引元组。当对Hash表进行扩展后,原来存放在溢出页中的索引元组可能会被移入到新增加的桶中,这时就需要对溢出页进行回收。

溢出页的分配:

 

 溢出页的回收

 

 

4. Hash表的扩展

每次插入后,用当前记录总数 r 和当前桶数目相除,计算 r/n ,若比率太大,则对Hash表进行扩展,即增加一个桶到Hash表中。如果新加入的桶的桶号二进制表示为1a2a3......ai,那么就试图分裂桶号为0a2a3......a的桶的元组,这个桶中部分元组(计算Hash值,取i+1位后,第一位为1的元组)将被移动到新的桶中

 

posted @ 2021-05-22 11:08  foreast  阅读(1096)  评论(0编辑  收藏  举报