【宗帅原创】PostgreSQL表扫描方法解析

发布于 2021-07-20 11:13

本文介绍PostgreSQL表扫描方法原理。

全表扫描函数在heapam_handler的接口函数为heap_getnextslot函数。该函数从磁盘上读取数据页到内存并将遍历页记录,将其存放到slot中返回。这个函数一次只获取一个记录,到达上层的ExecutePlan函数中循环调用ExecProcNode再次进入到heap_getnextslot函数获取下一个记录,依次类推,直到获得所有记录。

主要流程:

1、首先将入参TableScanDesc sscan强制转换类型到HeapScanDesc scan,后续向scan中保存扫描记录及状态

2、分两种页扫描:如果sscan->rs_flags为SO_ALLOW_PAGEMODE即扫描所有满足可见性的记录,调用函数heapgettup_pagemode;否则扫描所有记录,调用函数heapgettup

3、最后调用函数ExecStoreBufferHeapTuple将scan->rs_ctup存储到slot中。

4、这里首先说函数heapgettup_pagemode。这个函数根据三个扫描方向分别处理。只针对向前扫描说明:

  1)第一次进这个函数scan->rs_inited为false:从scan->rs_startblock页开始扫描,先调用函数heapgetpage函数获取磁盘页。这个函数得到的值保存到下面scan成员变量中:

scan->rs_cbuf:当前扫描的内存块块号

scan->rs_cblock:当前扫描的文件中页号

scan->rs_vistuples[]:保存可见记录的索引号,从1开始

scan->rs_ntuples:该页中有多少可见记录

此时就需要将scan->rs_inited置为TRUE,表示已初始化,再次进来时就不需要重复读取磁盘页了。

  2)扫描后续记录,scan->rs_cindex保存当前记录的索引号,获取下个记录只需加1

3)根据记录索引号从scan->rs_vistuples数组中取出记录索引号,然后从scan->rs_cbuf中获取具体tuple值,保存到scan->rs_ctup

4)如果根据key范围扫描,则需要比较key值,相等则返回,否则需要扫描下一个记录并进行比较。

5)如果不需要key值,则保存当前扫描记录的索引号后返回。

6)退出while循环,即当前页所有记录扫描完,获取下一页继续扫描直到所有页扫描完


heapgettup函数流程:

   1)首先取出scan->rs_ctup地址到tuple,后续记录值要保存到tuple中

2)同样分三种扫描方向:这里同样只针对向前扫描进行说明。

3)scan->rs_inited作为是否已初始化的依据,第一次进来时从scan->rs_startblock开始进行扫描,调用函数heapgetpage函数获取磁盘页。这个函数得到的值保存到下面scan成员变量中:

scan->rs_cbuf:当前扫描的内存块块号

scan->rs_cblock:当前扫描的文件中页号

scan->rs_vistuples[]:保存可见记录的索引号,从1开始[后面没有用这个]

scan->rs_ntuples:该页中有多少可见记录

从第一个记录开始扫描即lineoff=1,然后scan->rs_inited为TRUE,表示已初始化了

4)后续进来,扫描后续记录时,lineoff为当前记录的下一个索引号

5)需要对scan->rs_cbuf描述符的content_lock加BUFFER_LOCK_SHARE,后续扫描动作都在这个锁内执行

6)获取scan->rs_cbuf页内的对应记录的索引号lpp

7)该记录正常的话,获取记录值保存到tuple中;并且判断可见性,若可见则释放锁后返回;若通过key值扫描,则需比较。相等且可见时返回。

8)记录不可见或者key值不等,需要扫描下一个记录

9)while循环退出后,即该页的记录都扫描完,将scan->rs_cbuf的描述符的content_lock释放。

10)取下一页读入scan->rs_cbuf,并加锁,继续扫描这一页记录。

11)扫描完表的所有页,则for循环退出并返回

12)和heapgettup_pagemode区别是:都通过heapgetpage函数将页读到scan->rs_cbuf,并扫描其记录将可见的记录索引号保存到scan->rs_vistuples[]数组;不同在于,他后续锁内操作,遍历页内所有记录,不通过scan->rs_vistuples数组读取记录,然后再进行可见性判断及比较。MVCC情况下调用的函数是heapgettup_pagemode函数。