PG:表访问方法API如何进行顺序扫描?

yzsDBA

    引言
    PG中有很多方法检索数据并返回给用户。依赖于用户的SQL语句,查询计划模块生成最有方法以检索请求的数据。顺序扫描是用户请求大量数据时或者当表没有索引时使用的一种检索方法(例如select * from tablename;);顺序扫描方法由表的表访问方法APIchuli,heap表访问方法时当前版本中默认的方法。本文中,将会介绍表访问方法API如何进行顺序扫描。
    PG中表访问方法APIs
    PG12中引入了可拔插表访问方法,允许开发者重定义存储/检索表数据的方法。这个API包含42个函数。定义在tableam.h中,这些接口函数在typedef struct TableAmRoutine中。下面介绍关于顺序扫描的routine,帮助开发这了解如何创建自己的表访问方法。
    顺序扫描的调用栈
    42个routines中很少由一个会被执行器调用来完成顺序扫描的请求。本节按调用顺序描述这些接口。
    relation_size
    函数声明:uint64  (*relation_size) (Relation rel, ForkNumberforkNumber);
    Relation_size函数是第一个被调用的函数,相对简单。通过rel和forkNumber,返回对于文件的大学。默认heap表访问方法会调用存储管理器smgr,计算出对于表文件的页数,然后成语每个页大小BLCKSZ默认8KB。如果不确定表和forknumber之间的关系,可以查询网址https://www.highgo.ca/2020/10/23/free-space-mapping-file-in-details/了解更多信息。
    返回的大小设置顺序扫描的边界。
    slot_callbacks
    函数声明:const TupleTableSlotOps*(*slot_callbacks) (Relation rel);
    下一步,执行器需要找出此表访问方法与哪一个tuple table slot(TTS)回调操作的集合兼容。TTS是routines集合,确保tuple存储在执行器和访问方法之间兼容。执行器执行TTS回调以TupleTableSlot结构传输tuple,该结构执行器可以认识。默认的heap访问方法使用execTuples.c中定义的TTSOpsBufferHeapTuple来处理这个操作。
    scan_begin
    函数声明:TableScanDesc (*scan_begin) (Relationrel,
                                   Snapshotsnapshot,
                                   int nkeys,struct ScanKeyData *key,
                                  ParallelTableScanDesc pscan,
                                  uint32flags);
    现在可以开始扫描。这个函数是顺序扫描的初始化函数,将使用执行器传输的参数分配一个新的scan描述符。Scan描述符结构的目的在于执行顺序扫描时进行跟踪。例如,要跟踪从哪里开始扫描,上次扫描块号是什么时候,应该回复扫描哪个块,以及扫描了多少块等等。一旦顺序扫描完成,scan描述符会被销毁。
    执行器希望接口返回指向TableScanDesc结构的指针。
    scan_getnextslot
    函数声明:bool        (*scan_getnextslot) (TableScanDescscan,
                                       ScanDirection direction,
                                      TupleTableSlot *slot);
    这个函数是顺序扫描的主要函数,从buffer管理器中获取一个tuple,转换成TTS格式并将它存储到slot指针中。每次调用返回一个tuple。如果表由1000个tuple,这个函数会调用1000次。返回true表示需要再次调用获取下一个tuple,返回false表示获取了所有元组,不用再调用这个函数了。
    通常情况下顺序扫描以per-page模式进行工作。也就是说从buffer管理器中加载并读入一个block到内存,然后从这个页中一个元组一个元组地进行返回,获取完一页,再加载另一页进行扫描。
    Scan描述符起着重要作用,因为这个结构体中存有大量控制信息,调用scan_getnextslot会更新。
    scan_end
    函数声明:void        (*scan_end) (TableScanDesc scan);
    顺序扫描调用的最后一个函数,用于清理scan描述符。此时执行器已经通过顺序扫描方法获取了所有元组信息。
    准备返回的数据
    现在执行器通过表访问方法扫描了所有元组,需要进入过滤流程决定哪些元组符合返回的条件(例如使用WHERE限制扫描结果)。由execScan.c中的for循环在每个TTS上执行ExecQual,最终结果发送给用户。
    总结
    流程图如下: