所谓堆表,是指元组无序存储,数据按照“先来后到”的方式存储在页面中的空闲位置。作为对比,在索引表中,元组根据索引键键值的排序,在页面内部有序存储,且各个页面之间在逻辑上也是有序存储的。堆表存储数据主体,索引表仅存储索引键键值以及对应的、完整元组的物理位置(即完整元组在堆表中的页面号和页内偏移)。
HOT技术(堆内元组技术),在更新时通过修改指针指向定位新元组,而不需要插入相应的索引元组。
在更新tuple1时,会将tuple1(老元祖)的标记位置为heap_hot_update,同时tuple2(新元组)的标记位置为heap_only_tuple。
Heap_Only_tuple说明是hot-chain中的新元组
假设page中有个元组被多次hot更新,只有最老的元组是Heap_hot_Update
只有最新的元组是heap_only_tuple
其他元组既是Heap_hot_Update又是heap_only_tuple
下面对数据库中有关堆表操作的函数进行分析,介绍函数的作用和执行流程,具体细节可以结合openGauss的源码一起浏览。
堆表访问接口函数
Relation heap_open(Oid relationId, LOCKMODE lockmode, int2 bucketid)
#define heap_close(r,l) relation_close(r,l)
TableScanDesc heap_beginscan(Relation relation, Snapshot snapshot, int nkeys, ScanKey key, RangeScanInRedis rangeScanInRedis)
static HeapScanDesc heap_beginscan_internal(Relation relation, Snapshot snapshot, int nkeys, ScanKey key,uint32 flags, RangeScanInRedis rangeScanInRedis)
void heap_endscan(TableScanDesc sscan)
void heap_rescan(TableScanDesc sscan, ScanKey key)
HeapTuple heap_getnext(TableScanDesc sscan, ScanDirection direction)
static void heapgettup(HeapScanDesc scan, ScanDirection dir, int nkeys, ScanKey key)
static void heapgettup_pagemode(HeapScanDesc scan, ScanDirection dir, int nkeys, ScanKey key)
void heap_markpos(TableScanDesc sscan)
void heap_restrpos(TableScanDesc sscan)
void heapgetpage(TableScanDesc sscan, BlockNumber page)
Oid heap_insert(Relation relation, HeapTuple tup, CommandId cid, int options, BulkInsertState bistate)
int heap_multi_insert(Relation relation, Relation parent, HeapTuple *tuples, int ntuples, CommandId cid, int options,BulkInsertState bistate,HeapMultiInsertExtraArgs *args)
TM_Result heap_delete(Relation relation, ItemPointer tid, CommandId cid,Snapshot crosscheck, bool wait, TM_FailureData *tmfd, bool allow_delete_self)
void simple_heap_delete(Relation relation, ItemPointer tid, int options)
TM_Result heap_update(Relation relation, Relation parentRelation, ItemPointer otid, HeapTuple newtup,CommandId cid, Snapshot crosscheck, bool wait, TM_FailureData *tmfd, bool allow_update_self)
void simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
//判断当前时刻,元组htup是否有效
bool HeapTupleSatisfiesNow(HeapTuple htup, Snapshot snapshot, Buffer buffer)
TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple, Buffer* buffer, CommandId cid, LockTupleMode mode, bool nowait, TM_FailureData *tmfd, bool allow_lock_self)
void heap_inplace_update(Relation relation, HeapTuple tuple)
heap_open函数
参数:(Oid relationId, LOCKMODE lockmode, int2 bucketid)
数据表Oid,锁类型,桶id(可能是一个内存空间类似于buffer,从这个桶中进行查找数据表)
作用:通过数据表的Oid打开一个表,同时检查是否是索引或者混合类型,如果是报错,否则获取表的相关信息
1.调用relation_open函数,返回数据表信息,然后判断是否是索引或者混合类型,如果是,报错。
2.返回数据表信息。
heap_beginscan函数
作用:初始化扫描表描述信息,并且返回。
1.根据传入参数rangeScanInRedis.isRangeScanInRedis,判断是否需要报告位置给syncscan和是不是page-at-a-time模式,设置flags
2.调用heap_beginscan_internal函数
heap_beginscan_internal函数
作用:返回一个初始化完成的HeapScanDesc
参数:
(Relation relation, Snapshot snapshot, int nkeys, ScanKey key, uint32 flags, RangeScanInRedis rangeScanInRedis)
1.定义一个HeapScanDesc变量
2.分配空间,设置变量的一些参数。
3.调用initscan(scan, key, false);
initscan函数主要也是根据不同情况设置HeapScanDesc的参数,还拷贝了scan->rs_base.rs_key,也就是扫描关键字的描述数组。
4.返回这个变量。
heap_endscan函数
作用:结束关系扫描,释放扫描的缓存
1.获取表扫描描述符
2.释放缓存、关系引用次数减一、释放rs_base.rs_key(扫描关键字的描述数组)、释放访问策略
3.释放表扫描描述符。
heap_rescan函数
作用:重新初始化扫描描述符
调用initscan函数
heap_getnext函数
作用:返回扫描的下一条元组,通过scan->rs_ctup返回
1.根据是否是page-at-a-time模式,调用heapgettup_pagemode或者heapgettup函数。(扫描的重点,复杂)
2.返回scan->rs_ctup
heapgettup函数
作用:获取下一条元组
参数:HeapScanDesc scan, ScanDirection dir, int nkeys, ScanKey key
1.首先根据ScanDirection进行分类Forword、Backward、no movement
Forword:
(2).如果scan没有初始化,就先对其进行初始化,设置偏移量为第一条元组的偏移量
已经初始化的scan,设置偏移量line_off为下一条元组的偏移量
(3).获取当前page剩余的未扫描的偏移量lines_left
Backward:
(2).获取当前扫描的页码
(3).如果scan没有初始化,偏移量就是当前页最大偏移量,或则获取上一个偏移量元组。当前page剩余未扫描的偏移量lines_left就是当前设置的偏移量。
No movement(重新获取记录):
(2).重新获取当前扫描的元组,获取当前的偏移量
(3).返回
2.根据页号和偏移量获取行指针linepoint
3.先判断lines_left是否大于0,即当前page剩余未扫描的偏移量是否大于0,然后再判断获取的行指针linepoint是否有效,如果有效就获取linepoint对应的元组,设置参数,然后返回
4.如果找到的linepoint无效,那就再继续沿着扫描方向,查找下一个元组,直到找到有效的元组进入第三步,或者lines_left = 0。
5.lines_left = 0说明这一页已经全部扫描过了,那就寻找next_page,然后根据扫描方向,从头或者从尾开始扫描,直到找到有效的linepoint,或者页已经扫描完了返回null。