免费建设网站和域名,什么做直播网站,电商网站设计 页面转化率,做html网站搜索框教程执行算子#xff08;SeqScan算子#xff09; 执行算子概述扫描算子SeqScan算子ExecInitSeqScan函数InitScanRelation函数ExecSeqScan函数 总结 声明#xff1a;本文的部分内容参考了他人的文章。在编写过程中#xff0c;我们尊重他人的知识产权和学术成果#xff0c;力求遵… 执行算子SeqScan算子 执行算子概述扫描算子SeqScan算子ExecInitSeqScan函数InitScanRelation函数ExecSeqScan函数 总结 声明本文的部分内容参考了他人的文章。在编写过程中我们尊重他人的知识产权和学术成果力求遵循合理使用原则并在适用的情况下注明引用来源。 本文主要参考了 OpenGauss1.1.0 的开源代码和《OpenGauss数据库源码解析》一书 执行算子概述 在OpenGauss中执行算子是用于执行数据库查询计划的基本操作单元。执行算子负责处理查询计划中的每个节点执行各种操作从而实现用户查询的功能。 执行算子模块包含多种计划执行算子是计划执行的独立单元用于实现具体的计划动作。执行计划包含4类算子分别是控制算子、扫描算子、物化算子和连接算子。这些算子统一使用节点node表示具有统一的接口执行流程采用递归模式。整体执行流程是首先根据计划节点的类型初始化状态节点函数名为“ExecInit算子名”然后再回调执行函数函数名为“Exec算子名”最后是清理状态节点函数名为“ExecEnd算子名”。本文主要介绍扫描算子因为最近在做这方面的工作。 执行算子类型如下所示
算子类型说 明控制算子处理特殊执行流程如Union语句扫描算子用于扫描表对象从表中获取数据物化算子缓存中间执行结果到临时存储连接算子用于实现SQL中的各类连接操作通常包含 nested loop join、bash join、merge-sort join等
扫描算子 扫描算子用于表、结果集、链表子查询等结果遍历每次获取一条元组作为上层节点的输入。控制算子中的 BitmapAnd/BitmapOr 函数所需的位图与扫描算子索引扫描算子密切相关。主要包括顺序扫描SeqScan、索引扫描IndexScan、位图扫描BitmapHeapScan、位图索引扫描BitmapIndexScan、元组TID扫描TIDScan、子查询扫描SubqueryScan、函数扫描FunctionScan等。扫描算子如下表所示。
算子名称说 明SeqScan算子用于扫描基础表IndexScan算子对表的扫描使用索引加速元组获取BitmapIndexScan算子通过位图索引做扫描操作BitMapHeapScan算子通过位图获取实际元组TIDScan算子遍历元组的物理存储位置获取一个元组SubqueryScan算子子查询生成的子执行计划FunctionScan算子用于从函数返回的数据集中获取元组ValuesScan算子用于处理“Values·········”类型语句从值列表中输出元组CteScan算子用于处理With表达式对应的子查询WorkTableScan算子用于递归工作表元组输出PartIteratorScan算子用于支持分区表的 wise join
SeqScan算子 我们首先来看一下SeqScan算子SeqScan 算子是最基本的扫描算子对应 SeqScan 执行节点对应的代码源文件是“src/gausskernel/runtime/executor/nodeSeqScan.cpp”用于对基础表做顺序扫描。算子对应的主要函数如下表所示。
主要函数说 明ExecInitSeqScan初始化SeqScan状态节点ExecSeqScan迭代获取元组ExecEndSeqScan清理SeqScan状态节点ExecSeqMarkPos标记扫描位置ExecSeqRestrPos重置扫描位置ExecReScanSeqScan重置SeqScanInitScanRelation初始化扫描表 下面我们以一个案例来调试一下代码吧首先执行sql语句
postgres# create table t2 (id int not null, name varchar);
CREATE TABLE
postgres# insert into t2 values(1, Postgres);
INSERT 0 1
postgres# insert into t2 values(2, OpenGauss);
INSERT 0 1
postgres# select * from t2;ExecInitSeqScan函数 函数 ExecInitSeqScan 是 OpenGauss 数据库中用于初始化顺序扫描算子SeqScan的函数。它是执行计划中实际执行算子的初始化过程用于准备执行算子所需的状态和资源。在函数 ExecInitSeqScan 中打上断点进行调试 我们来解读一下 ExecInitSeqScan 函数的源码吧路径src/gausskernel/runtime/executor/nodeSeqscan.cpp 在ExecInitSeqScan 函数中有三个入参SeqScan* node, EState* estate, int eflags下面分别来解释一下他们是什么 SeqScan* node这是一个指向查询计划节点的指针表示要初始化的顺序扫描算子节点。这个节点包含了构建查询计划所需的信息如表信息、谓词条件等。EState* estate这是一个指向执行状态的指针表示当前查询的执行状态。执行状态包含了在查询执行过程中需要的各种上下文信息如内存管理、参数信息等。int eflags这是一个标志位表示执行的选项和参数。可以用来控制执行算子的行为如是否要在执行过程中收集统计信息等。 /* ----------------------------------------------------------------* ExecInitSeqScan* ----------------------------------------------------------------*/
SeqScanState* ExecInitSeqScan(SeqScan* node, EState* estate, int eflags)
{TableSampleClause* tsc NULL; /* 表样本子句 *//** 曾经有可能在 SeqScan 的 outerPlan 中但现在不再允许。*/Assert(outerPlan(node) NULL);Assert(innerPlan(node) NULL);/** 创建状态结构体*/SeqScanState* scanstate makeNode(SeqScanState);scanstate-ps.plan (Plan*)node; /* 计划节点 */scanstate-ps.state estate; /* 执行状态 */scanstate-isPartTbl node-isPartTbl; /* 是否是分区表 */scanstate-currentSlot 0; /* 当前迭代位置 */scanstate-partScanDirection node-partScanDirection; /* 分区扫描方向 */scanstate-rangeScanInRedis {false, 0, 0}; /* 分布时间的范围扫描 */if (!node-tablesample) {scanstate-isSampleScan false; /* 是否是表样本扫描 */} else {scanstate-isSampleScan true;tsc node-tablesample;}/** 杂项初始化** 为节点创建表达式上下文*/ExecAssignExprContext(estate, scanstate-ps);/** 初始化子表达式*/scanstate-ps.targetlist (List*)ExecInitExpr((Expr*)node-plan.targetlist, (PlanState*)scanstate);// 判断是否需要进行表样本扫描如果 node 结构中的 tablesample 成员非空就说明需要进行表样本扫描if (node-tablesample) {scanstate-sampleScanInfo.args (List*)ExecInitExpr((Expr*)tsc-args, (PlanState*)scanstate);scanstate-sampleScanInfo.repeatable ExecInitExpr(tsc-repeatable, (PlanState*)scanstate);scanstate-sampleScanInfo.sampleType tsc-sampleType; //设置表样本类型 sampleType/** 初始化 RowTableSample*/if (scanstate-sampleScanInfo.tsm_state NULL) {scanstate-sampleScanInfo.tsm_state New(CurrentMemoryContext) RowTableSample(scanstate);}}/** 元组表初始化*/ExecInitResultTupleSlot(estate, scanstate-ps);ExecInitScanTupleSlot(estate, scanstate);InitScanRelation(scanstate, estate);ADIO_RUN(){/* 添加预取相关信息 */scanstate-ss_scanaccessor (SeqScanAccessor*)palloc(sizeof(SeqScanAccessor));SeqScan_Init(scanstate-ss_currentScanDesc, scanstate-ss_scanaccessor, scanstate-ss_currentRelation);}ADIO_END();/** 初始化扫描关系*/// 调用InitSeqNextMtd函数初始化ScanState结构体中的ScanNextMtd函数指针用于执行具体的扫描操作。InitSeqNextMtd(node, scanstate);// 判断是否存在扫描描述符if (IsValidScanDesc(scanstate-ss_currentScanDesc)) {// 初始化并行扫描的参数包括指定的并行度、分区扫描方向等scan_handler_tbl_init_parallel_seqscan(scanstate-ss_currentScanDesc, scanstate-ps.plan-dop, scanstate-partScanDirection);} else {scanstate-ps.stubType PST_Scan;}// 表示扫描结果并不是直接从目标列表即查询的目标列中获取的而是从其他地方获取的scanstate-ps.ps_TupFromTlist false;/** 初始化结果元组类型和投影信息。*/ExecAssignResultTypeFromTL(scanstate-ps,scanstate-ss_currentRelation-rd_tam_type);ExecAssignScanProjectionInfo(scanstate);return scanstate;
} 以为下SeqScan* node的结构可以看到执行节点Node中的 type 字段的值为 T_SeqScan 这表示这个执行节点是一个顺序扫描Sequential Scan节点。 以为下EState* estate的结构而 EState* estate 中的 type 字段的值为 T_EState它表示这个结构体是一个执行状态Execution State的对象。在数据库查询执行过程中EState 存储了查询执行过程中的中间状态。 可以看到“ExecInitSeqScan” 函数的返回类型是 “SeqScanState” 结构SeqScanState 结构体是 OpenGauss 数据库中用于执行顺序扫描操作的状态信息的结构体。在数据库查询执行过程中每个执行节点都会有相应的状态结构体来存储节点执行过程中的中间状态、结果等信息。对于顺序扫描操作就是使用 SeqScanState 结构体来存储顺序扫描的状态信息。 SeqScanState 结构体定义如下路径src/include/nodes/execnodes.h
typedef struct ScanState {PlanState ps; /* 计划状态首字段是NodeTag */Relation ss_currentRelation; /* 当前关系 */TableScanDesc ss_currentScanDesc; /* 当前扫描描述符 */TupleTableSlot* ss_ScanTupleSlot; /* 扫描元组插槽 */bool ss_ReScan; /* 是否重扫描 */Relation ss_currentPartition; /* 当前分区关系 */bool isPartTbl; /* 是否是分区表 */int currentSlot; /* 当前迭代位置 */ScanDirection partScanDirection; /* 分区扫描方向 */List* partitions; /* 分区列表 */LOCKMODE lockMode; /* 锁模式 */List* runTimeParamPredicates; /* 运行时参数谓词 */bool runTimePredicatesReady; /* 运行时谓词是否准备好 */bool is_scan_end; /* 是否结束扫描用于扫描使用信息性约束的情况 */SeqScanAccessor* ss_scanaccessor; /* 预取相关 */int part_id; /* 分区ID */int startPartitionId; /* 并行线程的起始分区ID */int endPartitionId; /* 并行线程的结束分区ID */RangeScanInRedis rangeScanInRedis; /* 是否为分布时间的范围扫描 */bool isSampleScan; /* 是否是表样本扫描 */SampleScanParams sampleScanInfo; /* TABLESAMPLE 参数包括类型/种子/可重复性 */ExecScanAccessMtd ScanNextMtd; /* 扫描下一个方法 */
} ScanState;/** SeqScan uses a bare ScanState as its state node, since it needs* no additional fields.*/
typedef ScanState SeqScanState;其中ExecInitSeqScan 函数中的这段代码是在判断是否需要进行表样本扫描如果 node 结构中的 tablesample 成员非空就说明需要进行表样本扫描。 if (node-tablesample) {scanstate-sampleScanInfo.args (List*)ExecInitExpr((Expr*)tsc-args, (PlanState*)scanstate);scanstate-sampleScanInfo.repeatable ExecInitExpr(tsc-repeatable, (PlanState*)scanstate);scanstate-sampleScanInfo.sampleType tsc-sampleType; //设置表样本类型 sampleType/** 初始化 RowTableSample*/if (scanstate-sampleScanInfo.tsm_state NULL) {scanstate-sampleScanInfo.tsm_state New(CurrentMemoryContext) RowTableSample(scanstate);}}注解什么是表样本扫描 表样本扫描Table Sample Scan是一种数据库查询优化技术用于在大型表中进行随机或者有规律的采样以减少查询的执行时间。它适用于那些数据量庞大的表通过从表中抽取一小部分数据进行查询可以在不影响查询结果准确性的前提下显著减少查询所需的时间和资源消耗。 InitScanRelation函数 接着进入到 InitScanRelation 函数中进行初始化扫描表。InitScanRelation 函数源码如下路径src/gausskernel/runtime/executor/nodeSeqscan.cpp
/* ----------------------------------------------------------------* InitScanRelation** 此函数对扫描关系和扫描的子计划进行初始化。* ----------------------------------------------------------------*/
void InitScanRelation(SeqScanState* node, EState* estate)
{Relation current_relation; // 当前关系Relation current_part_rel NULL; // 当前分区关系SeqScan* plan NULL; // SeqScan计划节点bool is_target_rel false; // 是否为目标关系LOCKMODE lockmode AccessShareLock; // 锁模式TableScanDesc current_scan_desc NULL; // 当前扫描描述// 判断当前扫描操作是否针对目标关系即是否用于更新、删除等操作的目标表is_target_rel ExecRelationIsTargetRelation(estate, ((SeqScan*)node-ps.plan)-scanrelid);/** 从范围表的第scanrelid项获取关系对象id打开该关系并在其上获取适当的锁。*/current_relation ExecOpenScanRelation(estate, ((SeqScan*)node-ps.plan)-scanrelid);/** 元组表初始化*/ExecInitResultTupleSlot(estate, node-ps, current_relation-rd_tam_type);ExecInitScanTupleSlot(estate, node, current_relation-rd_tam_type);// 判断当前查询是否针对分区表partitioned table进行的。// node-isPartTbl 是一个标志用于指示当前查询是否是针对一个分区表。if (!node-isPartTbl) {/* 为redis添加限制条件 */// 初始化表扫描的描述符TableScanDesc以便开始执行表的扫描操作。current_scan_desc InitBeginScan(node, current_relation);} else {// 将节点的计划部分转换为SeqScan类型以便访问相关属性plan (SeqScan*)node-ps.plan;/* 初始化分区列表 */node-partitions NULL;// 如果当前关系不是目标关系则将锁模式设置为AccessShareLockif (!is_target_rel) {lockmode AccessShareLock;} else {switch (estate-es_plannedstmt-commandType) {case CMD_UPDATE:case CMD_DELETE:case CMD_MERGE:lockmode RowExclusiveLock;break;case CMD_SELECT:lockmode AccessShareLock;break;default:ereport(ERROR,(errcode(ERRCODE_INVALID_OPERATION),errmodule(MOD_EXECUTOR),errmsg(无效操作 %d 用于序列扫描的分区允许的操作为 UPDATE/DELETE/SELECT,estate-es_plannedstmt-commandType)));break;}}node-lockMode lockmode;/* 生成node-partitions如果存在 */if (plan-itrs 0) { // 如果有分区迭代数Partition part NULL;PruningResult* resultPlan NULL;// 如果分区裁剪信息中的表达式不为空获取分区信息if (plan-pruningInfo-expr ! NULL) {resultPlan GetPartitionInfo(plan-pruningInfo, estate, current_relation);} else {resultPlan plan-pruningInfo;}ListCell* cell NULL;List* part_seqs resultPlan-ls_rangeSelectedPartitions;// 遍历选中的分区序号foreach (cell, part_seqs) {Oid tablepartitionid InvalidOid;int partSeq lfirst_int(cell);// 根据序号获取分区的Oidtablepartitionid getPartitionOidFromSequence(current_relation, partSeq);// 打开分区part partitionOpen(current_relation, tablepartitionid, lockmode);// 将打开的分区加入到分区列表中node-partitions lappend(node-partitions, part);}// 设置分区数量if (resultPlan-ls_rangeSelectedPartitions ! NULL) {node-part_id resultPlan-ls_rangeSelectedPartitions-length;} else {node-part_id 0;}}// 如果有分区if (node-partitions ! NIL) {/* 构造第一个分区的HeapScanDesc */// 获取第一个分区Partition currentPart (Partition)list_nth(node-partitions, 0);// 获取分区关系current_part_rel partitionGetRelation(current_relation, currentPart);// 设置当前分区关系node-ss_currentPartition current_part_rel;/* 为redis添加限制条件 */// 初始化第一个分区的扫描描述符current_scan_desc InitBeginScan(node, current_part_rel);} else {// 如果没有分区则设置当前分区为NULLnode-ss_currentPartition NULL;// 初始化扫描条件node-ps.qual (List*)ExecInitExpr((Expr*)node-ps.plan-qual, (PlanState*)node-ps);}}// 将当前正在扫描的关系对象表赋值给SeqScanState结构体中的ss_currentRelation成员变量以表示当前扫描的是哪个关系。node-ss_currentRelation current_relation;// 将扫描描述符赋值给SeqScanState结构体中的ss_currentScanDesc成员变量以便后续的扫描操作可以使用该描述符。node-ss_currentScanDesc current_scan_desc;// 对扫描到的数据进行正确的解析和存储ExecAssignScanType(node, RelationGetDescr(current_relation));
}在上述代码中ExecRelationIsTargetRelation 函数作用是判断当前扫描操作是否针对目标关系即是否用于更新、删除等操作的目标表。ExecRelationIsTargetRelation 会检查给定的扫描计划节点是否是一个目标关系如果是的话返回true否则返回false。这里打印结果为false表示不是一个目标关系。 注解在数据库操作中一个目标关系通常是指执行更新UPDATE、删除DELETE或插入INSERT等修改数据操作的表。目标关系是被操作的主要表对其进行的修改操作会直接影响到表中的数据。而非目标关系则是在操作过程中可能涉及到的其他表但这些表不是直接受到操作影响的对象。 在本案例中执行 SQL 查询语句 SELECT * FROM t2; 时如果查询的是表 t2 并且没有进行更新、删除等修改操作那么这条查询语句并不会修改表的数据因此表 t2 就不是一个目标关系。因此打印结果为false。 而 InitBeginScan(node, current_relation) 函数作用是初始化表扫描的描述符TableScanDesc以便开始执行表的扫描操作。具体来说它会根据传入的参数 node 和 current_relation 生成一个用于表扫描的描述符。这个描述符包含了扫描相关的信息比如扫描的表、锁模式等。然后它将这个描述符返回以便后续的表扫描操作可以使用。 在这段代码中如果 node 不是分区表即 node-isPartTbl 为 false则调用 InitBeginScan 来初始化扫描描述符并将结果赋值给current_scan_desc。这意味着这段代码是在非分区表的情况下初始化表扫描描述符。如果是分区表就不执行这个操作。 源码及注释如下路径src/gausskernel/runtime/executor/nodeSeqscan.cpp
static TableScanDesc InitBeginScan(SeqScanState* node, Relation current_relation)
{// 声明一个变量来存储表扫描描述符TableScanDesc current_scan_desc NULL;// 如果不是表样本扫描if (!node-isSampleScan) {// 调用scan_handler_tbl_beginscan函数来初始化表扫描描述符// 参数包括要扫描的关系、当前事务的快照、初始块号、分布式查询所需的信息以及扫描状态current_scan_desc scan_handler_tbl_beginscan(current_relation, node-ps.state-es_snapshot, 0, NULL, (ScanState*)node);} else {// 如果是表样本扫描则调用InitSampleScanDesc函数来初始化表样本扫描描述符current_scan_desc InitSampleScanDesc((ScanState*)node, current_relation);}// 返回初始化后的表扫描描述符return current_scan_desc;
}ExecSeqScan函数 ExecSeqScan 函数是 OpenGauss 中用于执行序列扫描Sequential Scan操作的函数。它执行基于序列扫描的查询计划获取满足扫描条件的数据并将结果放入一个元组表槽TupleTableSlot中返回。 其函数调用关系如下 ExecSeqScan 函数实际上是通过调用通用的扫描函数 ExecScan 来执行具体的序列扫描操作。源码如下路径src/gausskernel/runtime/executor/nodeSeqscan.cpp
/* ----------------------------------------------------------------* 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.* ----------------------------------------------------------------*/
TupleTableSlot* ExecSeqScan(SeqScanState* node)
{return ExecScan((ScanState*)node, node-ScanNextMtd, (ExecScanRecheckMtd)SeqRecheck);
}其三个入参的描述如下所示 node: SeqScanState* 类型表示序列扫描操作的状态信息其中包含了查询计划的相关信息以及当前扫描的状态。node-ScanNextMtd: 是一个函数指针指向具体的序列扫描操作的实现函数用于获取下一个满足条件的元组。(ExecScanRecheckMtd)SeqRecheck: 一个函数指针指向重新检查操作的函数用于在扫描过程中需要重新检查的情况下执行。 TupleTableSlot 结构体是在数据库查询执行过程中用于存储元组数据的数据结构。它是一个用于临时存储从表中检索到的数据行元组的容器通常在查询执行的不同阶段中用于传递和处理数据。来看一看 TupleTableSlot 结构体长什么样吧路径src/include/executor/tuptable.h
typedef struct TupleTableSlot {NodeTag type; /* 节点类型用于标识数据结构类型 */bool tts_isempty; /* true 槽为空 */bool tts_shouldFree; /* 是否应该释放 tts_tuple 内存 */bool tts_shouldFreeMin; /* 是否应该释放 tts_mintuple 内存 */bool tts_slow; /* 用于 slot_deform_tuple 的保存状态 */Tuple tts_tuple; /* 物理元组如果是虚拟的则为 NULL */#ifdef PGXC/** PGXC 扩展用于支持从远程 Datanode 发送的元组。*/char* tts_dataRow; /* DataRow 格式的元组数据 */int tts_dataLen; /* 数据行的实际长度 */bool tts_shouldFreeRow; /* 是否应该释放 tts_dataRow 内存 */struct AttInMetadata* tts_attinmeta; /* 存储从 DataRow 中提取值所需的信息 */Oid tts_xcnodeoid; /* 从哪个节点获取的数据行的 Oid */MemoryContext tts_per_tuple_mcxt;
#endifTupleDesc tts_tupleDescriptor; /* 槽的元组描述 */MemoryContext tts_mcxt; /* 槽本身所在的内存上下文 */Buffer tts_buffer; /* 元组的缓冲区如果没有则为 InvalidBuffer */int tts_nvalid; /* tts_values 中的有效值数 */Datum* tts_values; /* 当前每个属性的值 */bool* tts_isnull; /* 当前每个属性的是否为 NULL 的标志 */MinimalTuple tts_mintuple; /* 最小元组如果没有则为 NULL */HeapTupleData tts_minhdr; /* 仅用于最小元组的情况下的工作空间 */long tts_off; /* 用于 slot_deform_tuple 的保存状态 */long tts_meta_off; /* 用于 slot_deform_cmpr_tuple 的保存状态 */TableAmType tts_tupslotTableAm; /* 槽的元组表类型 */
} TupleTableSlot;调试信息如下 其中TupleTableSlot 结构体中的属性 tts_values 可以用于存储元组的具体值。这是一个指向 Datum 数组的指针其中每个元素对应于元组的每个属性的值。在 PostgreSQL 中Datum 是一个通用的数据类型可以表示各种数据类型的值。 tts_isnull 是一个 bool 数组的指针用于标识对应属性是否为 NULL。如果 tts_isnull[i] 为 true表示属性值是 NULL如果为 false则表示属性值不是 NULL。 这两个数组一起构成了一个完整的元组的值和 NULL 信息的表示。通过遍历 tts_values 数组和 tts_isnull 数组可以访问元组的每个属性的值和是否为 NULL 的信息。 不理解没关系来看一个案例吧 假设有一个关系表 students 包含以下几个属性student_id整数、name字符串、age整数和 grade字符串。我们可以使用 TupleTableSlot 结构体来表示从该表中获取的元组。 假设我们从 students 表中获取了一个元组具体的值如下 属性值student_id101nameAliceage25gradeA 我们可以将这个元组的值存储在 TupleTableSlot 结构体的 tts_values 和 tts_isnull 数组中以便后续处理。具体表示如下 tts_values 数组
索引值索引 0101student_id索引 1Alicename索引 225age索引 3Agrade
tts_isnull 数组
索引值索引 0falsestudent_id 不为 NULL索引 1falsename 不为 NULL索引 2falseage 不为 NULL索引 3falsegrade 不为 NULL 通过这两个数组我们可以知道元组的每个属性的值和是否为 NULL进而在执行操作时进行处理和判断。 ExecScan 函数源码如下路径src/gausskernel/runtime/executor/execScan.cpp
/* ----------------------------------------------------------------* ExecScan** 使用指定的 访问方法 扫描关系根据全局变量 ExecDirection 返回下一个符合条件的元组。* 访问方法返回下一个元组execScan() 负责将返回的元组与限定条件进行检查。** 还必须提供一个 重新检查方法它可以检查关系的任意元组是否符合实现在访问方法内部的任何限定条件。** 条件* -- AMI 维护的 游标 定位在之前返回的元组。** 初始状态* -- 指定的关系已打开进行扫描以便 游标 定位在第一个符合条件的元组之前。* ----------------------------------------------------------------*/
TupleTableSlot* ExecScan(ScanState* node, ExecScanAccessMtd access_mtd, /* 返回元组的函数 */ExecScanRecheckMtd recheck_mtd)
{ExprContext* econtext NULL; /* 表达式计算上下文 */List* qual NIL; /* 限定条件 */ProjectionInfo* proj_info NULL; /* 投影信息 */ExprDoneCond is_done; /* 判断投影是否结束的标志 */TupleTableSlot* result_slot NULL; /* 存储结果的槽 */if (node-isPartTbl !PointerIsValid(node-partitions))return NULL;/** 获取来自节点的数据*/qual node-ps.qual;proj_info node-ps.ps_ProjInfo;econtext node-ps.ps_ExprContext;/** 如果既没有需要检查的限定条件也没有需要进行投影的操作直接跳过全部开销返回原始扫描元组。*/if (qual NULL proj_info NULL) {ResetExprContext(econtext);return ExecScanFetch(node, access_mtd, recheck_mtd);}/** 检查是否仍在从之前的扫描元组中投影出元组因为在投影表达式中存在函数返回集合的情况。如果是则尝试投影另一个。*/if (node-ps.ps_TupFromTlist) {Assert(proj_info); /* 如果不进行投影不会到达这里 */result_slot ExecProject(proj_info, is_done);if (is_done ExprMultipleResult)return result_slot;/* 完成了这个源元组... */node-ps.ps_TupFromTlist false;}/** hdfs* 通过使用信息性约束来优化扫描。* 如果 is_scan_end 为 true则迭代结束。*/if (node-is_scan_end) {return NULL;}/** 重置每个元组内存上下文以释放在上一个元组循环中分配的表达式计算存储。* 注意这只有在我们完成了从扫描元组投影出元组后才能发生。*/ResetExprContext(econtext);/** 从访问方法中获取一个元组。循环直到获取到符合条件的元组为止。*/for (;;) {TupleTableSlot* slot NULL;CHECK_FOR_INTERRUPTS();slot ExecScanFetch(node, access_mtd, recheck_mtd);/* 在每次循环中刷新限定条件 */qual node-ps.qual;/** 如果 accessMtd 返回的 slot 包含 NULL那么意味着没有更多要扫描的元组因此我们只返回一个空的槽* 要小心使用投影结果槽以便其具有正确的 tupleDesc。*/if (TupIsNull(slot) || unlikely(executorEarlyStop())) {if (proj_info ! NULL)return ExecClearTuple(proj_info-pi_slot);elsereturn slot;}/** 将当前元组放入表达式上下文中*/econtext-ecxt_scantuple slot;/** 检查当前元组是否符合限定条件** 在此检查非空限定条件以避免在限定条件为空时调用 ExecQual() 函数的开销... 节省一些循环周期但它们会累积...*/if (qual NULL || ExecQual(qual, econtext, false)) {/** 找到了满足条件的扫描元组。*/if (proj_info ! NULL) {/** 构造投影元组将其存储在结果元组槽中并返回除非我们发现无法从这个扫描元组中投影出元组* 在这种情况下继续扫描。*/result_slot ExecProject(proj_info, is_done);
#ifdef PGXC/* 如果底层扫描的槽具有 xcnodeoid则复制 xcnodeoid */result_slot-tts_xcnodeoid slot-tts_xcnodeoid;
#endif /* PGXC */if (is_done ! ExprEndResult) {node-ps.ps_TupFromTlist (is_done ExprMultipleResult);/** hdfs* 通过使用信息性约束来优化外部扫描。*/if (IsA(node-ps.plan, ForeignScan)) {ForeignScan* foreign_scan (ForeignScan*)(node-ps.plan);if (foreign_scan-scan.scan_qual_optimized) {/** 如果找到了合适的元组则设置 is_scan_end 值为 true。* 这意味着在下一次迭代中找不到合适的元组迭代结束。*/node-is_scan_end true;}}return result_slot;}} else {/** 通过使用信息性约束来优化外部扫描。*/if (IsA(node-ps.plan, ForeignScan)) {ForeignScan* foreign_scan (ForeignScan*)(node-ps.plan);if (foreign_scan-scan.scan_qual_optimized) {/** 如果找到了合适的元组则设置 is_scan_end 值为 true。* 这意味着在下一次迭代中找不到合适的元组迭代结束。*/node-is_scan_end true;}}/** 在这里我们不进行投影因此直接返回扫描元组。*/return slot;}} elseInstrCountFiltered1(node, 1);/** 元组不符合限定条件因此释放每个元组内存并重试。*/ResetExprContext(econtext);}
}在执行查询计划时扫描节点会通过 ExecScan 函数调用 ExecScanFetch 函数来获取符合条件的元组然后再进行投影、过滤等操作。 ExecScanFetch 函数的核心任务是从关系中获取下一个元组以及根据重新检查方法检查该元组是否满足内部的限定条件。这个函数的逻辑会根据当前的扫描方向正向或反向和访问方法的实现来决定如何获取下一个元组。 我们来详细的看一看 ExecScanFetch 函数到底是怎样运行的吧 ExecScanFetch 函数源码如下路径src/gausskernel/runtime/executor/execScan.cpp
/** ExecScanFetch -- 获取下一个潜在元组** 这个函数主要用于在 EvalPlanQual 重新检查时替换测试元组。* 如果不是在 EvalPlanQual 中就执行访问方法的下一个元组操作。*/
static TupleTableSlot* ExecScanFetch(ScanState* node, ExecScanAccessMtd access_mtd, ExecScanRecheckMtd recheck_mtd)
{// 从node的执行状态结构体中获取执行状态estateEState* estate node-ps.state;// 检查是否存在 EvalPlanQual执行计划中的计划块即是否正在执行 EvalPlanQual 重新检查if (estate-es_epqTuple ! NULL) {/** 我们在 EvalPlanQual 重新检查中。如果有测试元组可用就在重新检查任何访问方法特定条件后返回该测试元组。*/Index scan_rel_id ((Scan*)node-ps.plan)-scanrelid;Assert(scan_rel_id 0);if (estate-es_epqTupleSet[scan_rel_id - 1]) {TupleTableSlot* slot node-ss_ScanTupleSlot;/* 如果我们已经返回了一个元组则返回空槽 */if (estate-es_epqScanDone[scan_rel_id - 1])return ExecClearTuple(slot);/* 否则标记以记住不应该再返回更多元组 */estate-es_epqScanDone[scan_rel_id - 1] true;/* 如果没有测试元组就返回空槽 */if (estate-es_epqTuple[scan_rel_id - 1] NULL)return ExecClearTuple(slot);/* 将测试元组存储在计划节点的扫描槽中 */(void)ExecStoreTuple(estate-es_epqTuple[scan_rel_id - 1], slot, InvalidBuffer, false);/* 检查是否满足访问方法的条件 */if (!(*recheck_mtd)(node, slot))(void)ExecClearTuple(slot); /* 不会被扫描返回 */return slot;}}/** 运行节点类型特定的访问方法函数来获取下一个元组*/return (*access_mtd)(node);
}再来看看 (*access_mtd)(node); 做了什么SeqNext 函数是执行顺序扫描的核心工作函数。它从扫描描述中获取扫描信息和状态然后通过调用 scan_handler_tbl_getnext 函数从表中获取下一个元组。获取到的元组会被保存到扫描元组槽中并返回该槽。在这个过程中还涉及到预取操作以及缓冲区引用计数的处理。 函数指针定义如下路径src/gausskernel/runtime/executor/nodeSeqscan.cpp
static TupleTableSlot* SeqNext(SeqScanState* node);/* ----------------------------------------------------------------* Scan Support* ----------------------------------------------------------------*/
/* ----------------------------------------------------------------* SeqNext** 这是 ExecSeqScan 的工作函数* ----------------------------------------------------------------*/
static TupleTableSlot* SeqNext(SeqScanState* node)
{Tuple tuple;TableScanDesc scanDesc;EState* estate NULL;ScanDirection direction;TupleTableSlot* slot NULL;/** 从 estate 和扫描状态中获取信息*/scanDesc node-ss_currentScanDesc;estate node-ps.state;direction estate-es_direction;slot node-ss_ScanTupleSlot;GetTableScanDesc(scanDesc, node-ss_currentRelation)-rs_ss_accessor node-ss_scanaccessor;/** 从表中获取下一个元组用于顺序扫描。*/tuple scan_handler_tbl_getnext(scanDesc, direction, node-ss_currentRelation);ADIO_RUN(){Start_Prefetch(GetTableScanDesc(scanDesc, node-ss_currentRelation), node-ss_scanaccessor, direction);}ADIO_END();/** 将返回的元组和缓冲区保存在扫描元组槽中并返回该槽。* 注意我们传递 false因为由 heap_getnext() 返回的元组是指向磁盘页上的指针不是使用 palloc() 创建的* 因此不应使用 pfree_ext() 进行释放。另外注意ExecStoreTuple 将增加缓冲区的引用计数引用计数将不会减少* 直到清除元组表槽为止。*/return ExecMakeTupleSlot(tuple, GetTableScanDesc(scanDesc, node-ss_currentRelation), slot, node-ss_currentRelation-rd_tam_type);
} SeqNext 函数中 使用 GetTableScanDesc 函数来处理分桶表Bucketed Table的情况。GetTableScanDesc 获取表扫描描述符TableScanDesc的函数它根据输入的扫描描述符和关系表来返回对应的有效的扫描描述符。 注解分桶表Bucketed Table是一种数据库表的存储组织方式主要用于在分布式数据库系统中提高查询性能和并行处理能力。分桶表将表的数据按照某个列的值分成多个桶bucket每个桶包含相近的数据从而可以使查询和分析操作更加高效。 函数源码如下路径src/gausskernel/storage/access/hbstore/hbucket_am.cpp
TableScanDesc GetTableScanDesc(TableScanDesc scan, Relation rel)
{if (scan ! NULL rel ! NULL RELATION_CREATE_BUCKET(scan-rs_rd)) {return (TableScanDesc)((HBktTblScanDesc)scan)-currBktScan;} else {return scan;}
}该函数返回一个 TableScanDesc 类型的指针表示有效的扫描描述符。调试结果如下 SeqNext 函数调用 ExecMakeTupleSlot 函数主要用于将获取到的元组tuple存储到一个元组表槽TupleTableSlot中同时关联元组的信息如元组来自的扫描描述符、关联的缓冲区等。这个函数在数据库查询执行过程中扮演了重要的角色用于将从数据库表中获取的数据以一种可管理的形式存储在内存中以便后续的处理和返回。 函数定义如下路径src/gausskernel/runtime/executor/execTuples.cpp
TupleTableSlot* ExecMakeTupleSlot(Tuple tuple, TableScanDesc tableScan, TupleTableSlot* slot, TableAmType tableAm)
{if (unlikely(RELATION_CREATE_BUCKET(tableScan-rs_rd))) {tableScan ((HBktTblScanDesc)tableScan)-currBktScan;}if (tuple ! NULL) {Assert(tableScan ! NULL);slot-tts_tupslotTableAm tableAm;return ExecStoreTuple(tuple, /* tuple to store */slot, /* slot to store in */tableScan-rs_cbuf, /* buffer associated with this tuple */false); /* dont pfree this pointer */}return ExecClearTuple(slot);
}在函数 ExecMakeTupleSlot 中ExecStoreTuple 函数用于将一个元组存储到一个 TupleTableSlot 中。TupleTableSlot 是一种用于存储元组数据的数据结构通常用于在查询计划的不同节点之间传递元组数据。 源码如下路径src/gausskernel/runtime/executor/execTuples.cpp
/* --------------------------------* ExecStoreTuple** This function is used to store a physical tuple into a specified* slot in the tuple table.** tuple: tuple to store* slot: slot to store it in* buffer: disk buffer if tuple is in a disk page, else InvalidBuffer* shouldFree: true if ExecClearTuple should pfree_ext() the tuple* when done with it** If buffer is not InvalidBuffer, the tuple table code acquires a pin* on the buffer which is held until the slot is cleared, so that the tuple* wont go away on us.** shouldFree is normally set true for tuples constructed on-the-fly.* It must always be false for tuples that are stored in disk pages,* since we dont want to try to pfree those.** Another case where it is false is when the referenced tuple is held* in a tuple table slot belonging to a lower-level executor Proc node.* In this case the lower-level slot retains ownership and responsibility* for eventually releasing the tuple. When this method is used, we must* be certain that the upper-level Proc node will lose interest in the tuple* sooner than the lower-level one does! If youre not certain, copy the* lower-level tuple with heap_copytuple and let the upper-level table* slot assume ownership of the copy!** Return value is just the passed-in slot pointer.** NOTE: before PostgreSQL 8.1, this function would accept a NULL tuple* pointer and effectively behave like ExecClearTuple (though you could* still specify a buffer to pin, which would be an odd combination).* This saved a couple lines of code in a few places, but seemed more likely* to mask logic errors than to be really useful, so its now disallowed.* --------------------------------*/
TupleTableSlot* ExecStoreTuple(Tuple tuple, TupleTableSlot* slot, Buffer buffer, bool should_free)
{/** sanity checks*/Assert(tuple ! NULL);Assert(slot ! NULL);Assert(slot-tts_tupleDescriptor ! NULL);tableam_tslot_store_tuple(tuple, slot, buffer, should_free);return slot;
}总结 ExecInitSeqScan 函数初始化 SeqScan 状态节点负责节点状态结构构造并初始化用于存储结果的元组表。 ExecSeqScan 函数是 SeqScan 算子执行的主体函数用于迭代获取每一个元组。ExecSeqScan 函数通过回调函数调用SeqNext 函数、HbktSeqSampleNext 函数、SeqSampleNext 函数实现获取元组。非采样获取元组时调用 SeqNext 函数如果需要采样且对应的表采用哈希桶方式存储则调用 HbktSeqSampleNext 函数否则调用 SeqSampleNext 函数。 以下是 SeqScan 算子的执行过程的完整阐述 查询解析和分析 在 PostgreSQL 中查询首先经过解析和分析阶段确定了要查询的表、选择的列以及过滤条件等。执行计划生成 根据解析和分析的结果系统生成一个查询执行计划。在这个阶段PostgreSQL 的查询优化器会考虑各种访问路径、连接顺序和操作顺序以生成一个优化的执行计划。初始化执行环境 执行计划生成后会为查询执行创建一个执行环境EState其中包括查询的状态信息、内存上下文等。此时还会初始化一些查询中使用到的辅助数据结构。初始化 SeqScanState 对于 SeqScan 算子会创建一个 SeqScanState 结构用于存储该算子的执行状态。这个结构包含了 ScanState 的通用字段以及一些特定于 SeqScan 的字段如当前的扫描状态、分区信息等。初始化扫描关系 SeqScan 需要初始化扫描关系即要从哪个表中扫描数据。这包括打开表、获取相关的元数据等操作。初始化元组插槽 在查询执行过程中需要使用 TupleTableSlot 来存储和传递元组数据。SeqScanState 中会初始化一个 TupleTableSlot用于存储从表中读取的元组数据。执行扫描 正式进入执行阶段SeqScan 开始逐行地扫描数据。具体步骤如下 获取下一个扫描元组通过底层的存储访问接口获取表中的下一个元组。将元组存储到插槽将扫描到的元组存储到初始化的 TupleTableSlot 中 以便后续操作使用。应用过滤条件如果查询中存在过滤条件WHERE 子句会对当前扫描到的元组应用过滤条件确定是否满足条件。如果满足条件元组会被返回给上层的操作节点进行进一步处理例如投影、排序等。如果不满足条件会继续扫描下一个元组。 结束扫描 一旦所有的元组都被扫描完毕或者查询的其他操作已经得到了结果SeqScan 算子的执行就会结束。这时会释放相关的资源关闭表清理内存等操作。返回结果 执行完成后根据查询的需要可能会返回一个结果集或者进行其他的操作如数据插入、更新等。
文章转载自: http://www.morning.rqckh.cn.gov.cn.rqckh.cn http://www.morning.xbtlt.cn.gov.cn.xbtlt.cn http://www.morning.ysgnb.cn.gov.cn.ysgnb.cn http://www.morning.lfdrq.cn.gov.cn.lfdrq.cn http://www.morning.qjghx.cn.gov.cn.qjghx.cn http://www.morning.wmmjw.cn.gov.cn.wmmjw.cn http://www.morning.btlmb.cn.gov.cn.btlmb.cn http://www.morning.lsnnq.cn.gov.cn.lsnnq.cn http://www.morning.gbxxh.cn.gov.cn.gbxxh.cn http://www.morning.wtsr.cn.gov.cn.wtsr.cn http://www.morning.dmlsk.cn.gov.cn.dmlsk.cn http://www.morning.qfmns.cn.gov.cn.qfmns.cn http://www.morning.zmnyj.cn.gov.cn.zmnyj.cn http://www.morning.rdpps.cn.gov.cn.rdpps.cn http://www.morning.yslfn.cn.gov.cn.yslfn.cn http://www.morning.ghwdm.cn.gov.cn.ghwdm.cn http://www.morning.nzzws.cn.gov.cn.nzzws.cn http://www.morning.jxlnr.cn.gov.cn.jxlnr.cn http://www.morning.cpqqf.cn.gov.cn.cpqqf.cn http://www.morning.ttvtv.cn.gov.cn.ttvtv.cn http://www.morning.darwallet.cn.gov.cn.darwallet.cn http://www.morning.ptxwg.cn.gov.cn.ptxwg.cn http://www.morning.mlycx.cn.gov.cn.mlycx.cn http://www.morning.bsghk.cn.gov.cn.bsghk.cn http://www.morning.fpqq.cn.gov.cn.fpqq.cn http://www.morning.c7510.cn.gov.cn.c7510.cn http://www.morning.niukaji.com.gov.cn.niukaji.com http://www.morning.cbnxq.cn.gov.cn.cbnxq.cn http://www.morning.hsjfs.cn.gov.cn.hsjfs.cn http://www.morning.ykyfq.cn.gov.cn.ykyfq.cn http://www.morning.nckjk.cn.gov.cn.nckjk.cn http://www.morning.cpzkq.cn.gov.cn.cpzkq.cn http://www.morning.ljbch.cn.gov.cn.ljbch.cn http://www.morning.wynnb.cn.gov.cn.wynnb.cn http://www.morning.slwfy.cn.gov.cn.slwfy.cn http://www.morning.ggnrt.cn.gov.cn.ggnrt.cn http://www.morning.nwfpl.cn.gov.cn.nwfpl.cn http://www.morning.byxs.cn.gov.cn.byxs.cn http://www.morning.kdjtt.cn.gov.cn.kdjtt.cn http://www.morning.kqpq.cn.gov.cn.kqpq.cn http://www.morning.thzgd.cn.gov.cn.thzgd.cn http://www.morning.tndxg.cn.gov.cn.tndxg.cn http://www.morning.jyznn.cn.gov.cn.jyznn.cn http://www.morning.zrrgx.cn.gov.cn.zrrgx.cn http://www.morning.bpmnx.cn.gov.cn.bpmnx.cn http://www.morning.smygl.cn.gov.cn.smygl.cn http://www.morning.jnzfs.cn.gov.cn.jnzfs.cn http://www.morning.xyhql.cn.gov.cn.xyhql.cn http://www.morning.hlyfn.cn.gov.cn.hlyfn.cn http://www.morning.jzklb.cn.gov.cn.jzklb.cn http://www.morning.zhffz.cn.gov.cn.zhffz.cn http://www.morning.qpsdq.cn.gov.cn.qpsdq.cn http://www.morning.wjhdn.cn.gov.cn.wjhdn.cn http://www.morning.sxhdzyw.com.gov.cn.sxhdzyw.com http://www.morning.fqpyj.cn.gov.cn.fqpyj.cn http://www.morning.bzlsf.cn.gov.cn.bzlsf.cn http://www.morning.jljiangyan.com.gov.cn.jljiangyan.com http://www.morning.nbsfb.cn.gov.cn.nbsfb.cn http://www.morning.thlr.cn.gov.cn.thlr.cn http://www.morning.kflbf.cn.gov.cn.kflbf.cn http://www.morning.wkgyz.cn.gov.cn.wkgyz.cn http://www.morning.pfggj.cn.gov.cn.pfggj.cn http://www.morning.qqzdr.cn.gov.cn.qqzdr.cn http://www.morning.lmrcq.cn.gov.cn.lmrcq.cn http://www.morning.mjjty.cn.gov.cn.mjjty.cn http://www.morning.mprtj.cn.gov.cn.mprtj.cn http://www.morning.chmcq.cn.gov.cn.chmcq.cn http://www.morning.mztyh.cn.gov.cn.mztyh.cn http://www.morning.ypdhl.cn.gov.cn.ypdhl.cn http://www.morning.lmfxq.cn.gov.cn.lmfxq.cn http://www.morning.xzqzd.cn.gov.cn.xzqzd.cn http://www.morning.xsetx.com.gov.cn.xsetx.com http://www.morning.bkgfp.cn.gov.cn.bkgfp.cn http://www.morning.nqwz.cn.gov.cn.nqwz.cn http://www.morning.ssxlt.cn.gov.cn.ssxlt.cn http://www.morning.nxbkw.cn.gov.cn.nxbkw.cn http://www.morning.tpqrc.cn.gov.cn.tpqrc.cn http://www.morning.rqbr.cn.gov.cn.rqbr.cn http://www.morning.elbae.cn.gov.cn.elbae.cn http://www.morning.bpmtq.cn.gov.cn.bpmtq.cn