SeqScan节点代码处于src/backend/executor/nodeSeqscan.c文件中,包含了4个重要函数:ExecInitSeqScan、ExecSeqScan、ExecReScanSeqScan和 ExecEndSeqScan。
ExecInitSeqScan
src/backend/executor/nodeSeqscan.c文件中的ExecInitSeqScan函数,其主要包含如下步骤,如上图红色所标出:
- 创建SeqScanState结构体,关联执行计划树上的SeqScan节点,关联estate
- 创建expression context
- 打开scan relation,根据行类型创建合适的槽(PostgreSQL数据库TableAM——HeapAM TupleTableSlot类型中描述了src/backend/access/table/tableam.c向外部模块提供了table_slot_callbacks函数用于获取tableAM创建的表适合的数据tuple存储槽),并将槽关联到estate的es_tupleTable List中
- 初始化结果类型和投影
- 初始化child experssions
SeqScanState *ExecInitSeqScan(SeqScan *node, EState *estate, int eflags){
/* create state structure */
SeqScanState *scanstate = makeNode(SeqScanState);
scanstate->ss.ps.plan = (Plan *) node; scanstate->ss.ps.state = estate; scanstate->ss.ps.ExecProcNode = ExecSeqScan;
/* create expression context for node */
ExecAssignExprContext(estate, &scanstate->ss.ps);
/* open the scan relation */
scanstate->ss.ss_currentRelation = ExecOpenScanRelation(estate, node->scanrelid, eflags);
/* and create slot with the appropriate rowtype */
ExecInitScanTupleSlot(estate, &scanstate->ss, RelationGetDescr(scanstate->ss.ss_currentRelation), table_slot_callbacks(scanstate->ss.ss_currentRelation));
/* Initialize result type and projection. */
ExecInitResultTypeTL(&scanstate->ss.ps);
ExecAssignScanProjectionInfo(&scanstate->ss);
/* initialize child expressions */
scanstate->ss.ps.qual = ExecInitQual(node->plan.qual, (PlanState *) scanstate);
return scanstate;
}
ExecSeqScan
ExecSeqScan函数顺序扫描表,返回下一个合适的元组。ExecScan函数会调用SeqNext函数,该函数是真正从表中获取元组的工作函数。
/* ----------------------------------------------------------------
* ExecSeqScan(node)
* Scans the relation sequentially and returns the next qualifying
* tuple.
* We call the ExecScan() routine and pass it the appropriate
* access method functions.
* ----------------------------------------------------------------
*/
static TupleTableSlot *ExecSeqScan(PlanState *pstate){
SeqScanState *node = castNode(SeqScanState, pstate);
return ExecScan(&node->ss, (ExecScanAccessMtd) SeqNext, (ExecScanRecheckMtd) SeqRecheck);
}
SeqNext会做三件事:1. 如果不存在scandesc则创建并初始化一个scandesc(这在顺序扫描中会发生,显而易见的是懒加载,只有真正执行时才创建,但是在并行seqscan中,在Gather节点执行函数ExecGather/ExecGatherMerge中的ExecInitParallelPlanExecSeqScanInitializeDSM函数会调用了TableAM提供的table_beginscan_parallel函数,该函数会初始化ParallelTableScanDescData结构体,并调用TableAM begin_scan接口创建TableScanDesc结构体。)由于SeqNext一次只返回一条可见元组,所以需要一个迭代器,用于记录当前遍历到了哪一个缓存页的哪一条元组,scandesc就是这个迭代器。scandesc是一个HeapScanDesc类型的结构体。2. 调用heap_getnext获取一条可见元组,该函数也是全表遍历的一个核心函数,可以参见HeapAM对TableAM的getnextslot的实现。
static TupleTableSlot *SeqNext(SeqScanState *node){
/* get information from the estate and scan state */
TableScanDesc scandesc = node->ss.ss_currentScanDesc;
EState *estate = node->ss.ps.state;
ScanDirection direction = estate->es_direction;
TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
if (scandesc == NULL) { /* We reach here if the scan is not parallel, or if we're serially executing a scan that was planned to be parallel. */
scandesc = table_beginscan(node->ss.ss_currentRelation, estate->es_snapshot, 0, NULL);
node->ss.ss_currentScanDesc = scandesc;
}
/* get the next tuple from the table */
if (table_scan_getnextslot(scandesc, direction, slot)) return slot;
return NULL;
}
https://blog.csdn.net/obvious__/article/details/120706222