2021SC@SDUSC
上一篇博客讲了关于Hash索引创建与插入的相关函数,这一篇博客讲述关于溢出页的操作函数以及Hash表的扩展相关的函数。

目录

  • 溢出页的分配和回收
    • _hash_addovflpage函数
    • _hash_freeovflpage函数
  • Hash表的扩展
    • _hash_expandtable
    • _hash_splitbucket
    • _hash_squeezebucket
  • 总结

溢出页的分配和回收

上一篇博客中讲到插入函数时,我注意到有关溢出页的相关操作,如果空间不够则会开辟新的溢出页,用到的关于溢出页的重要函数为_hash_addovflpage和_hash_freeovflpage函数,这对于hash索引也是尤其重要的。因此我们来看一下溢出页的分配与回收的相关操作,其中关于锁的处理也是极其繁杂的。

_hash_addovflpage函数

该函数位于hashovfl.c中,因为源码部分过长,所以我提前分析了该函数的大体过程总结如下,
1.寻找一个空闲的溢出页
2.获取分配给Hash表的总页数,以及最后一个位图的位置和第一个空闲溢出页的位图
3.双层循环,遍历每一个位图页的每一个标志位看看是否有溢出页没有在in use
4.如果有空闲的溢出页,就将其标记修改为1。如果没有空闲的溢出页就申请一个新的溢出页,并更新有关它的信息
5.初始化溢出页,将溢出页连接到桶最后,返回溢出页的缓冲区号
我们来分析一下它的源码

Buffer
_hash_addovflpage(Relation rel, Buffer metabuf, Buffer buf, bool retain_pin)
{Buffer     ovflbuf;//声明相关的变量Page       page;Page       ovflpage;HashPageOpaque pageopaque;HashPageOpaque ovflopaque;HashMetaPage metap;Buffer      mapbuf = InvalidBuffer;Buffer      newmapbuf = InvalidBuffer;BlockNumber blkno;uint32     orig_firstfree;uint32       splitnum;uint32    *freep = NULL;uint32        max_ovflpg;uint32       bit;uint32      bitmap_page_bit;uint32      first_page;uint32       last_bit;uint32     last_page;uint32        i,j;bool        page_found = false;//锁定顺序//1.开始在桶的尾页有写锁//2.需要在桶尾页面获取锁给元页面上锁//3.在元页面查找位图页,如果找到则释放元页面的锁,获得新的溢出缓冲区的锁.LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);/*在hashutil.c文件中,主要检查页面是否冗余以及是否符合hash格式*/_hash_checkpage(rel, buf, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);for (;;){BlockNumber nextblkno;page = BufferGetPage(buf);pageopaque = (HashPageOpaque) PageGetSpecialPointer(page);nextblkno = pageopaque->hasho_nextblkno;if (!BlockNumberIsValid(nextblkno))break;/* 假设不需要编写未修改的页面*/if (retain_pin){//Pin只需要保留在初始桶Assert((pageopaque->hasho_flag & LH_PAGE_TYPE) == LH_BUCKET_PAGE);LockBuffer(buf, BUFFER_LOCK_UNLOCK);}else_hash_relbuf(rel, buf);retain_pin = false;buf = _hash_getbuf(rel, nextblkno, HASH_WRITE, LH_OVERFLOW_PAGE);}/* 获取元页上的独占锁 */LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);_hash_checkpage(rel, metabuf, LH_META_PAGE);metap = HashPageGetMeta(BufferGetPage(metabuf));/* 初始条件,开始迭代循环寻找位图页中0位置*/orig_firstfree = metap->hashm_firstfree;first_page = orig_firstfree >> BMPG_SHIFT(metap);bit = orig_firstfree & BMPG_MASK(metap);i = first_page;j = bit / BITS_PER_MAP;bit &= ~(BITS_PER_MAP - 1);/* 迭代每个位页图 ,双层循环,寻找空闲*/for (;;){BlockNumber mapblkno;Page        mappage;uint32      last_inpage;/* 如果都搜索完那么结束寻找 */splitnum = metap->hashm_ovflpoint;max_ovflpg = metap->hashm_spares[splitnum] - 1;last_page = max_ovflpg >> BMPG_SHIFT(metap);last_bit = max_ovflpg & BMPG_MASK(metap);if (i > last_page)break;Assert(i < metap->hashm_nmaps);mapblkno = metap->hashm_mapp[i];if (i == last_page)last_inpage = last_bit;elselast_inpage = BMPGSZ_BIT(metap) - 1;/*在读位图页时释放元页的独占锁 */LockBuffer(metabuf, BUFFER_LOCK_UNLOCK);mapbuf = _hash_getbuf(rel, mapblkno, HASH_WRITE, LH_BITMAP_PAGE);mappage = BufferGetPage(mapbuf);freep = HashPageGetBitmap(mappage);for (; bit <= last_inpage; j++, bit += BITS_PER_MAP){if (freep[j] != ALL_SET)//如果有空闲的空间{page_found = true;/*重新获取元页上的独占锁 */LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);/* 将位数转化页内位数*/bit += _hash_firstfreebit(freep[j]);bitmap_page_bit = bit;//对于bit的一系列转化bit += (i << BMPG_SHIFT(metap));/* 计算回收溢出页的地址*/blkno = bitno_to_blkno(metap, bit);/* 获取并初始化回收的页面*/ovflbuf = _hash_getinitbuf(rel, blkno);goto found;//直接去到下面代码的found:进行相应处理}}/* 没有足够的空间则遍历下一个位图页 */_hash_relbuf(rel, mapbuf);mapbuf = InvalidBuffer;i++;j = 0;                  bit = 0;/* 重新获取元页上的独占锁*/LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);}//两层循环结束/*
如果没有了空闲的页面,那么需要添加新的 溢出页*/if (last_bit == (uint32) (BMPGSZ_BIT(metap) - 1)){bit = metap->hashm_spares[splitnum];/* 如果超过可建立的最大值,就会报错 */if (metap->hashm_nmaps >= HASH_MAX_BITMAPS)ereport(ERROR,(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),errmsg("out of overflow pages in hash index \"%s\"",RelationGetRelationName(rel))));newmapbuf = _hash_getnewbuf(rel, bitno_to_blkno(metap, bit), MAIN_FORKNUM);}else{}/* 计算新的溢出页的地址*/bit = BufferIsValid(newmapbuf) ?metap->hashm_spares[splitnum] + 1 : metap->hashm_spares[splitnum];blkno = bitno_to_blkno(metap, bit);/*
_hash_getnewbuf获取页面,smgr思想*/ovflbuf = _hash_getnewbuf(rel, blkno, MAIN_FORKNUM);found://无论是找到和新建都要进行这一步/*
进行更新*/START_CRIT_SECTION();if (page_found)//如果找到空闲页面{Assert(BufferIsValid(mapbuf));/*标记成正在使用 */SETBIT(freep, bitmap_page_bit);MarkBufferDirty(mapbuf);}else{/*更新计数,因为添加了新的溢出页 */metap->hashm_spares[splitnum]++;if (BufferIsValid(newmapbuf)){_hash_initbitmapbuffer(newmapbuf, metap->hashm_bmsize, false);MarkBufferDirty(newmapbuf);/* 对于元页中记录的关于溢出页和位图页的相关信息更新*/metap->hashm_mapp[metap->hashm_nmaps] = BufferGetBlockNumber(newmapbuf);metap->hashm_nmaps++;metap->hashm_spares[splitnum]++;}MarkBufferDirty(metabuf);}/** Adjust hashm_firstfree to avoid redundant searches.  But don't risk* changing it if someone moved it while we were searching bitmap pages.*/if (metap->hashm_firstfree == orig_firstfree){metap->hashm_firstfree = bit + 1;MarkBufferDirty(metabuf);}/* initialize new overflow page */ovflpage = BufferGetPage(ovflbuf);ovflopaque = (HashPageOpaque) PageGetSpecialPointer(ovflpage);ovflopaque->hasho_prevblkno = BufferGetBlockNumber(buf);ovflopaque->hasho_nextblkno = InvalidBlockNumber;ovflopaque->hasho_bucket = pageopaque->hasho_bucket;ovflopaque->hasho_flag = LH_OVERFLOW_PAGE;ovflopaque->hasho_page_id = HASHO_PAGE_ID;MarkBufferDirty(ovflbuf);/*将新创建的溢出页链接到上一页 */pageopaque->hasho_nextblkno = BufferGetBlockNumber(ovflbuf);MarkBufferDirty(buf);/*xlog的相关处理*/if (RelationNeedsWAL(rel)){XLogRecPtr recptr;xl_hash_add_ovfl_page xlrec;xlrec.bmpage_found = page_found;xlrec.bmsize = metap->hashm_bmsize;XLogBeginInsert();XLogRegisterData((char *) &xlrec, SizeOfHashAddOvflPage);XLogRegisterBuffer(0, ovflbuf, REGBUF_WILL_INIT);XLogRegisterBufData(0, (char *) &pageopaque->hasho_bucket, sizeof(Bucket));XLogRegisterBuffer(1, buf, REGBUF_STANDARD);if (BufferIsValid(mapbuf)){XLogRegisterBuffer(2, mapbuf, REGBUF_STANDARD);XLogRegisterBufData(2, (char *) &bitmap_page_bit, sizeof(uint32));}if (BufferIsValid(newmapbuf))XLogRegisterBuffer(3, newmapbuf, REGBUF_WILL_INIT);XLogRegisterBuffer(4, metabuf, REGBUF_STANDARD);XLogRegisterBufData(4, (char *) &metap->hashm_firstfree, sizeof(uint32));recptr = XLogInsert(RM_HASH_ID, XLOG_HASH_ADD_OVFL_PAGE);PageSetLSN(BufferGetPage(ovflbuf), recptr);PageSetLSN(BufferGetPage(buf), recptr);if (BufferIsValid(mapbuf))PageSetLSN(BufferGetPage(mapbuf), recptr);if (BufferIsValid(newmapbuf))PageSetLSN(BufferGetPage(newmapbuf), recptr);PageSetLSN(BufferGetPage(metabuf), recptr);}END_CRIT_SECTION();if (retain_pin)//对于锁和缓冲区的相关处理LockBuffer(buf, BUFFER_LOCK_UNLOCK);else_hash_relbuf(rel, buf);if (BufferIsValid(mapbuf))_hash_relbuf(rel, mapbuf);LockBuffer(metabuf, BUFFER_LOCK_UNLOCK);if (BufferIsValid(newmapbuf))_hash_relbuf(rel, newmapbuf);return ovflbuf;
}

_hash_freeovflpage函数

该函数是为了对于无用的溢出页进行释放,由于该函数源码较长,我先总结了关于该函数的主要流程
1.获取需要回收的溢出页的信息
2. 将溢出页从桶链中断开,需要获取到需要回收的溢出页的前一页和后一页
3. 将需要回收的溢出页前一页和后一页用指针连接起来
4. 修改位图页和元页关于该溢出页的相关信息

BlockNumber
_hash_freeovflpage(Relation rel, Buffer bucketbuf, Buffer ovflbuf,Buffer wbuf, IndexTuple *itups, OffsetNumber *itup_offsets,Size *tups_size, uint16 nitups,BufferAccessStrategy bstrategy)
{HashMetaPage metap;//进行相关的初始化Buffer        metabuf;Buffer      mapbuf;BlockNumber ovflblkno;BlockNumber prevblkno;BlockNumber blkno;BlockNumber nextblkno;BlockNumber writeblkno;HashPageOpaque ovflopaque;Page        ovflpage;Page       mappage;uint32     *freep;uint32        ovflbitno;int32     bitmappage,bitmapbit;Bucket     bucket PG_USED_FOR_ASSERTS_ONLY;Buffer      prevbuf = InvalidBuffer;Buffer     nextbuf = InvalidBuffer;bool       update_metap = false;/*获取要回收的溢出页来获取相关信息 */_hash_checkpage(rel, ovflbuf, LH_OVERFLOW_PAGE);ovflblkno = BufferGetBlockNumber(ovflbuf);ovflpage = BufferGetPage(ovflbuf);ovflopaque = (HashPageOpaque) PageGetSpecialPointer(ovflpage);nextblkno = ovflopaque->hasho_nextblkno;prevblkno = ovflopaque->hasho_prevblkno;writeblkno = BufferGetBlockNumber(wbuf);bucket = ovflopaque->hasho_bucket;/*
在回收该溢出页时需要对桶链进行相关的修复,因为是双向的,断开要回收的溢出页,需要断开指针并增加新的指针*/if (BlockNumberIsValid(prevblkno))//该需要回收的溢出页前一个页面是有效的,获得相关的缓冲区信息{if (prevblkno == writeblkno)prevbuf = wbuf;elseprevbuf = _hash_getbuf_with_strategy(rel,prevblkno,HASH_WRITE,LH_BUCKET_PAGE | LH_OVERFLOW_PAGE,bstrategy);}if (BlockNumberIsValid(nextblkno))//如果该需要回收的溢出页是有效的,获得相关的缓冲区信息nextbuf = _hash_getbuf_with_strategy(rel,nextblkno,HASH_WRITE,LH_OVERFLOW_PAGE,bstrategy);//读取元页面metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_READ, LH_META_PAGE);metap = HashPageGetMeta(BufferGetPage(metabuf));/*确定修改位图页的哪个标志位 */ovflbitno = _hash_ovflblkno_to_bitno(metap, ovflblkno);bitmappage = ovflbitno >> BMPG_SHIFT(metap);bitmapbit = ovflbitno & BMPG_MASK(metap);if (bitmappage >= metap->hashm_nmaps)elog(ERROR, "invalid overflow bit number %u", ovflbitno);blkno = metap->hashm_mapp[bitmappage];/* 进入位图页时释放元页的锁*/LockBuffer(metabuf, BUFFER_LOCK_UNLOCK);/* 进入位图页修改相关的标志位 */mapbuf = _hash_getbuf(rel, blkno, HASH_WRITE, LH_BITMAP_PAGE);mappage = BufferGetPage(mapbuf);freep = HashPageGetBitmap(mappage);Assert(ISSET(freep, bitmapbit));/* 得到写锁修改元页信息 */LockBuffer(metabuf, BUFFER_LOCK_EXCLUSIVE);/* WAL相关内容 */if (RelationNeedsWAL(rel))XLogEnsureRecordSpace(HASH_XLOG_FREE_OVFL_BUFS, 4 + nitups);START_CRIT_SECTION();if (nitups > 0){_hash_pgaddmultitup(rel, wbuf, itups, itup_offsets, nitups);MarkBufferDirty(wbuf);}_hash_pageinit(ovflpage, BufferGetPageSize(ovflbuf));ovflopaque = (HashPageOpaque) PageGetSpecialPointer(ovflpage);ovflopaque->hasho_prevblkno = InvalidBlockNumber;ovflopaque->hasho_nextblkno = InvalidBlockNumber;ovflopaque->hasho_bucket = -1;ovflopaque->hasho_flag = LH_UNUSED_PAGE;ovflopaque->hasho_page_id = HASHO_PAGE_ID;MarkBufferDirty(ovflbuf);
//更新指针关系,要回收的溢出页去掉后,前一页需要和去掉后的溢出页的后一页互相有指针if (BufferIsValid(prevbuf)){Page        prevpage = BufferGetPage(prevbuf);HashPageOpaque prevopaque = (HashPageOpaque) PageGetSpecialPointer(prevpage);//前一页的处理Assert(prevopaque->hasho_bucket == bucket);prevopaque->hasho_nextblkno = nextblkno;//指向后一页MarkBufferDirty(prevbuf);}if (BufferIsValid(nextbuf)){Page      nextpage = BufferGetPage(nextbuf);HashPageOpaque nextopaque = (HashPageOpaque) PageGetSpecialPointer(nextpage);Assert(nextopaque->hasho_bucket == bucket);nextopaque->hasho_prevblkno = prevblkno;//指向前一页MarkBufferDirty(nextbuf);}/* 把标志位设置为0证明回收的溢出页已经空闲 */CLRBIT(freep, bitmapbit);MarkBufferDirty(mapbuf);/* 如果他不是第一个空闲页面,那么更新firstfree */if (ovflbitno < metap->hashm_firstfree){metap->hashm_firstfree = ovflbitno;update_metap = true;MarkBufferDirty(metabuf);}return nextblkno;
}

Hash表的扩展

在每次插入postgresql的hash表时,都需要记录当前的总数以及桶的数目,如果两者比率过大,就会对hash表进行扩展,与hash表扩展相关的函数有hash,我们这篇博客主要介绍_hash_expandtable函数 ,_hash_splitbucket函数以及_hash_squeezebucket函数的大体内容,源码由于过多过长,在这里不展开一一分析了,我将大体过程总结如下。

_hash_expandtable

1计算相关的数据看是否要进行需要增加桶并进行分裂
2计算需要增加的新桶的桶号
3计算待分裂的桶的桶号
4修改元页的数据
5将分裂桶的一部分元组放到新桶中,此时会调用_hash_splitbucket函数

 _hash_splitbucket(rel, metabuf,old_bucket, new_bucket,buf_oblkno, buf_nblkno, NULL,maxbucket, highmask, lowmask);_hash_dropbuf(rel, buf_oblkno);_hash_dropbuf(rel, buf_nblkno);

_hash_splitbucket

1初始化新桶new_bucket,将ooffnum为原桶的第一个元组,计算其对应的桶号
2如果桶号等于新桶那么看新桶是否有足够的空间,如果有插入到新桶,删掉原来的。

         if (bucket == nbucket){IndexTuple new_itup;new_itup = CopyIndexTuple(itup);

3如果没有那么申请一个新的溢出页
4找下一个ooffnum,循环迭代。

_hash_squeezebucket

该函数的作用是为了压缩桶中的元组,使桶中的元组更加紧凑,其大致流程图如下所示

总结

Hash索引的相关内容大致就是这些,分为三篇讲述了关于postgresql的Hash索引的相关思想,创建插入相关函数,溢出页分配和回收以及hash表的扩展,充分展现了PostgreSQL中关于Hash索引的相关并发处理,锁的使用,多层函数调用,再一次感受到了postgresql源码的结构层次以及逻辑的严密性。

postgreSQL源码分析——索引的建立与使用——Hash索引(3)相关推荐

  1. PostgreSQL源码分析

    PostgreSQL源码结构 PostgreSQL的使用形态 PostgreSQL采用C/S(客户机/服务器)模式结构.应用层通过INET或者Unix Socket利用既定的协议与数据库服务器进行通信 ...

  2. postgreSQL源码分析——索引的建立与使用——GIST索引(2)

    2021SC@SDUSC 本篇博客主要讲解GiST索引创建以及删除的相关函数 这里写目录标题 GIST创建 相关数据结构 GISTBuildState GISTInsertStack gistbuil ...

  3. postgreSQL源码分析——索引的建立与使用——Hash索引(2)

    2021SC@SDUSC 目录 Hash索引创建 hashbuild函数 _hash_init函数 Hash索引的插入 hashinsert函数 _hash_doinsert函数 总结 Hash索引创 ...

  4. postgreSQL源码分析——索引的建立与使用——各种索引类型的管理和操作(2)

    2021SC@SDUSC 目录 上层操作函数 index_open index_beginscan() index_create() indexcmd.c 下层接口函数 IndexScanDescDa ...

  5. postgreSQL源码分析——索引的建立与使用——Hash索引(1)

    2021SC@SDUSC 目录 Hash索引 Hash索引原理 Hash表 Hash索引结构 Hash的页面结构 元页 桶页,溢出页,位图页 和B-Tree相比的优缺点 优点 缺点 总结 Hash索引 ...

  6. postgreSQL源码分析——索引的建立与使用——各种索引类型的管理和操作(1)

    2021SC@SDUSC 目录 概述 管理索引的系统表 记录索引相关的系统表 与索引系统表相关的后端源码 索引的操作函数 上层操作函数 下层接口函数 概述 索引是指按表中某些关键属性或表达式建立元组的 ...

  7. postgreSQL源码分析——索引的建立与使用——总结篇

    2021SC@SDUSC 在小组中我负责索引的建立与使用的相关部分,在此一共写了16篇相关的分析报告,着重分析各种索引的操作和管理方法,以及分析了PG中四种最重要的索引B-Tree索引,Hash索引, ...

  8. postgreSQL源码分析——索引的建立与使用——B-Tree索引(3)

    2021SC@SDUSC 目录 B-Tree的插入 bt_insert _bt_doinsert BTInsertStateData _bt_search函数 _bt_moveright函数 B-Tr ...

  9. postgreSQL源码分析——索引的建立与使用——B-Tree索引(2)

    2021SC@SDUSC 目录 B-Tree建立过程 IndexAmRoutine BTBuildState BTWriteState btbuild() _bt_leafbuild _bt_load ...

最新文章

  1. python 模拟微信浏览器请求_使用Chrome修改user agent模拟微信内置浏览器
  2. 科大星云诗社动态20210910
  3. 翻译:SQL Server中的索引内部结构:到SQL Server索引级别10的阶梯。
  4. 解决vc 6在vista下的一些兼容问题
  5. jQuery基础(未完待续)
  6. Java 基础——类和对象
  7. 详解优酷视频质量评价体系
  8. 《深入体验Java Web开发内幕——核心基础》目录
  9. 第二十二章:面向对象(2)
  10. 17.EXTJs 中icon 与iconCls的区别及用法!
  11. python 小兵(2)
  12. ajax post提交数据_第三十五天JavaScript中的ajax
  13. 大麦无线虚拟服务器,解答大麦盒子无线设置的问题
  14. Matlab数值计算差商与插值
  15. Leetcode Day10 最长公共子序列+字符串交织
  16. 深度学习第三天-卷积神经网络(CNN):乳腺癌识别
  17. LeetCode 第 194 场周赛
  18. 校园访客登记管理系统设计与实现 java
  19. 这些低调、不耍流氓优的质软件!网友直呼:个个都是良心之作
  20. IDEA 各个图标含义,C图标、I图标、m图标、f图标

热门文章

  1. ffmpeg 2.6.3在Windows系统MinGW的编译
  2. 匹配特殊字符的正则表达式
  3. 为什么python发展的好_为什么Python发展这么快,有哪些优势?
  4. 【netty】Netty并发工具-Promise
  5. 【算法】剑指 Offer 67. 把字符串转换成整数
  6. 95-40-032-java.util.concurrent-ConcurrentHashMap
  7. 【Clickhouse】Clickhouse 访问控制和账号管理
  8. 【MySQL】MySQL 8不支持查询缓存
  9. 95-30-025-java.util-AbstractMap
  10. 代码结构中Dao,Service,Controller,Util,Model意思和划分