文中附图参考至《PostgreSQL数据库内核分析》node
(一)概念描述数据库
B+树是一种索引数据结构,其一个特征在于非叶子节点用于描述索引,而叶子节点指向具体的数据存储位置。在PostgreSQL中,存在结构类似的BTree索引,该数据结构最早引用于《Effiicient Locking for Concurrent Operations on B-Trees》论文,一个新特征在于,引入了“High Key”(下述HK)用于描述当前节点子节点的最大值。以下图所示:数组

其中K1表明一个HK,其值等于P0及P0子节点的最大值,对于上述存在的2n个节点,每一个节点都存在一个指针指向右兄弟节点,Pi的子节点取值范围为(Ki-1,Ki]数据结构
(二)PostgreSQL的BTree索引结构并发
在PostgreSQL中,普通表的表文件组织结构以下图app

其中Linp结构用来指向文件块中的一个元组。Freespace是未分配的空闲空间,对于新插入页面的元组及其对应的Linp元素从该空间进行分配,分配方式是Linp元素从Freespace的头部分配,tuple从尾部分配。而PostgreSQL的索引结构,也是按照上述页面结构进行存储的。以下图:函数

itup是排好序的索引元组,对于其如何完成排序将在以后的代码分析中进行介绍。linp用于索引itup,其存储了每一个itup在页面中的实际位置。根据PostgreSQL中对BTree索引结构的描述,分为当前节点是不是最右节点两种类型。因为非最右节点须要一个字段来保存HK,故当对一个页面进行填充时,存在着如下两种方式:源码分析
(1)当前节点为非最右节点ui

a.首先将itup3(最大的索引元组)复制到当前节点的右兄弟节点,而后将linp0指向itup3(HK)spa
b.去掉linp3。使用linp0来指向页面中的HK。
(2)当前节点为最右节点

对于最右节点,其并不须要HK,故将每一个linp递减一个位置,linp3再也不使用。
基于上述,PostgreSQL所实现的BTree索引组织结构以下图:

总结上图:
(1)对于非叶子节点,itup指向下一个节点,而对于叶子节点,itup指向实际物理存储的位置。
(2)Special space中,实现了两个指针,分配用于指向左右兄弟节点。
(3)根据BTree的特性,索引元组为有序,第一个叶子节点中itup3实际为最大索引元组,即HK,第二个叶子节点中itup1实际为最小索引元组,二者相同,故指向了同一物理存储位置。
(三)源码分析
- btbuild
索引建立的入口函数
- BTBuildState buildstate
定义并初始化buildstate结构。用于保存索引元组
- IndexBuildHeapScan
扫描表元组,并将其封装为索引元组。函数返回构建好的索引元组个数,返回函数指针buildstate
- while(... != NULL)
依次遍历基表的全部元组
- _bt_leafbuild
将buildstate中获得的索引元组构建为索引结构
- BTWriteState wstate
定义并初始化该结构。用于保存整个索引建立过程的信息。
- tuplesort_performsort
对索引元组进行排序
- qsort_ssup or qsort_tuple
在内存中对索引元组进行排序。
- _bt_load
对已排好序的索引元组,顺序读出将其插入到btree索引结构中
- BTPageState *state
定义并初始化该结构,在btree中,每一层仅有一个BTPageState结构,记录每层节点的信息
- if (merge)
若是spool2不为空,即if条件成立,则将spool与spool2进行归并排序
- else
spool2为空,则索引元组都存放在spool结构中
- while(依次取出spool中的每一个索引元组)
- _bt_buildadd
将每一个索引元组添加到索引结构
- if(页面已满)
- Page opage = npage, npage = _bt_blnewpage()
设置旧页面为当前页面,并从新分配新页面做为右兄弟节点
- _bt_buildadd
这里比较巧妙的利用递归,从当前已分配的页面开始,完成后续索引数组的插入
- _bt_blwritepage
将旧页面的信息写入索引文件。流程到这里,确定是递归函数已返回,因为旧页面已完成填充,不会再进行修改,则将其写入到索引文件中
- 页面未满
- _bt_sortaddup
将当前索引元组插入到页面中
- state->btps_page = npage
设置当前页面
- state->btps_blkno = nblko
设置当前磁盘块
- state->btps_lastoff = last_off
设置当前页面内偏移位置
- _bt_uppershutdown
构建每层节点的最右节点与父节点的连接关系
- _bt_initmetapage
在最右节点与父节点关系构建完成后,定义元页,每一个btree索引结构由一个元页结构记录信息
- btinsert
在已创建的索引基础上,插入一个新元素
- index_form_tuple
将表元组首先封装为索引元组
- _bt_doinsert
将索引元组插入到索引
- _bt_mkscankey
计算元组的扫描键值scan_key
- _bt_search
查找包含索引元组的页面
- _bt_getroot
获取索引结构的根节点
- for()
- _bt_moveright
并发性考虑
- if (当前节点为叶子节点)
跳出循环
- _bt_binsrch
不为叶子节点,在当前页面找到合适的元组itup
- new_stack
分配新的BTStack结构,将当前页面信息入栈
- if (惟一索引)
进行惟一性检查
- _bt_findinsertloc
在当前页面查找索引元素合适的插入位置
- _bt_insertonpg
插入索引元组
- if (当前页面没有足够的剩余空间)
- _bt_findsplitloc
遍历当前页面节点,查找最佳分裂点
- _bt_split
查找到该分裂点,对其进行分裂
- _bt_insert_parent
把新节点信息插入到父节点中
- else(当前页面有存够的剩余空间)
直接插入节点
总结:上述给出了关于btree构建与在已构建的btree中插入新元素时的函数实现流程。实现逻辑思想参考(一)(二)。其中对于函数_bt_moveright,其用于解决并发访问下的问题,如当前所操做页面正好是另外一事务操做被分裂的页面,则在当前页面返回所得结果后,须要查找其右兄弟页面,来返回所得的正确结果。