跟我一起读postgresql源码(九)——Executor(查询执行模块之——Scan节点(上))

从前面介绍的可优化语句处理相关的背景知识、实现思想和执行流程,不难发现可优化语句执行的核心内容是对于各种计划节点的处理,由于使用了节点表示、递归调用、统一接口等设计,计划节点的功能相对独立、代码总体流程相似,下面介绍执行器中各种计划节点的相关执行过程。

在PostgreSQL中,计划节点分为四类,分别是控制节点(Control Node)、扫描节点(ScanNode),物化节点(Materialization Node)、连接节点(Join Node) 。

控制节点:是一类用于处理特殊情况的节点,用于实现特殊的执行流程。例如,Result节点可用来表示INSERT语句中VALUES子句指定的将要插人的元组。

扫描节点:顾名思义,此类节点用于扫描表等对象以从中获取元组。例如,SeqScan节点用于顺序扫描一个表.毎次扫描一个元组。

物化节点:这类节点种类比较复杂,但它们有一个共同特点,即能够缓存执行结果到辅助存储中。物化节点会在第一次被执行时生成其中的所有结果元组,然后将这些结果元组缓存起来,等待其上层节点取用;而非物化节点则是每次被执行时生成一个结果元组并返回给上层节点。例如,Sort节点能够获取下层节点返回的所有元组并根据指定的属性进行排序,并将排序结果全部缓存起来,每次上层节点从Sort节点取元组时就从缓存中按顺序返回下一个元组(见Postgres中的物化节点之sort节点)。

连接节点:此类节点对应于关系代数中的连接操作,可以实现多种连接方式(条件连接、左连接、右连接、全连接、自然连接等),每种节点实现一种连接算法。例如,HashJoin实现了基于Hash的连接箅法。

扫描节点

扫描节点的作用是扫描表,每次获取一条元组作为上层节点的输入。扫描节点普遍存在于查询计划树的叶子节点,它不仅可以扫描表,还可以扫描函数的结果集、链表结构、子查询结果集等。

所有扫描节点都使用Scan作为公共父类,Scan不仅继承了Plan的所有属性,还扩展定义了scanrelid用于记录被扫描的表在范围表中的序号。

typedef struct Scan { Plan plan; Index scanrelid; /* relid is index into the range table */ } Scan;

扫描节点的执行状态节点都以ScanState作为公共父类,ScanState除了继承PlanState的所有属性之外,还扩展定义了ss_currentScanDesc(记录扫描的位置、关系等信息),currentRelation(记录被扫描的关系)和ss_ScanTupleSlot(记录扫描到的结果)。

typedef struct ScanState { PlanState ps; /* its first field is NodeTag */ Relation ss_currentRelation; HeapScanDesc ss_currentScanDesc; TupleTableSlot *ss_ScanTupleSlot; } ScanState;

下面是来自源码中的所有的Scan类型:

T_SeqScanState, T_SampleScanState, T_IndexScanState, T_IndexOnlyScanState, T_BitmapIndexScanState, T_BitmapHeapScanState, T_TidScanState, T_SubqueryScanState, T_FunctionScanState, T_ValuesScanState, T_CteScanState, T_WorkTableScanState, T_ForeignScanState, T_CustomScanState,

下面将对其一一说明。

扫描节点有各自的执行函数,但是这些执行函数都由公共的执行函数ExecScan来实现。

TupleTableSlot * ExecScan(ScanState *node, ExecScanAccessMtd accessMtd, /* function returning a tuple */ ExecScanRecheckMtd recheckMtd)

ExecScan需要三个参数:

状态节点ScanState,

获取扫描元组的函数指针(accessMtd,由于每一种扫描节点扫描的对象不同,因此函数都不同),

判断元组是否满足符合过滤条件的函数指针(recheckMtd)。这个要说一下:这个函数用于并发控制,如果当前元组被其他事物修改并已提交,需要检测该元组是否仍然满足选择条件。

ExecScan迭代地扫描对象,每次执行返回一条结果(内部返回元组是通过ExecScanFetch实现的)。ExecScan会使用accessMtd获取元组,然后recheckMtd进行过滤条件判断,最终返回元组。

看例子:

EXPLAIN SELECT * FROM tenk1 WHERE unique1 < 100; QUERY PLAN ------------------------------------------------------------------------------ Bitmap Heap Scan on tenk1 (cost=5.07..229.20 rows=101 width=244) Recheck Cond: (unique1 < 100) <---recheckMtd 的作用 -> Bitmap Index Scan on tenk1_unique1 (cost=0.00..5.04 rows=101 width=0) Index Cond: (unique1 < 100 1.SeqScan 节点

SeqScan是最基本的扫描节点,它用于扫描物理表,完成没有索引辅助的顺序扫描过程。其计划节点SeqScan实际是Scan节点的一个别名,并未定义扩展属性。其执行状态节点SeqScanState也直接使用ScanState。

SeqScan节点的初始化由函数ExecInitSeqScan完成。该函数首先创建一个SeqScanState结构,将SeqScan节点链接在SeqScanState结构的ps字段中。然后调用ExecInitExpr对计划节点的目标属性和査询条件进行初始化,并将它们链接到SeqScanState相应的字段中。接下来还将为计划节点分配用于存储结果元组和扫描元组的数据结构。最后通过计划节点中scanrelid字段的信息获取被扫描对象的RelationData结构,并链接在ss_currentRelation字段中,同时利用该信息调用heap_beginscan初始化扫描描述符ss_currentScanDesc

SeqScan节点的执行函数是ExecSeqScan,在该函数中:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wppxfp.html