---
title: openGauss数据库源码解析系列文章——执行器解析（三）-官方技术文章-鲲鹏社区
description: 本篇主要介绍“向量化引擎”的相关内容，包括控制算子、扫描算子、物化算子以及连接算子等。
keywords: Gauss,数据库源码解,析系列文章,执行器解析,官方技术文章,鲲鹏社区,向量化引擎,控制算子
url: https://www.hikunpeng.com/developer/techArticles/20230906-2?envFlag=1
section: (其他)
---

# openGauss数据库源码解析系列文章——执行器解析（三）-官方技术文章-鲲鹏社区

URL: https://www.hikunpeng.com/developer/techArticles/20230906-2?envFlag=1
描述: 本篇主要介绍“向量化引擎”的相关内容，包括控制算子、扫描算子、物化算子以及连接算子等。
关键词: Gauss,数据库源码解,析系列文章,执行器解析,官方技术文章,鲲鹏社区,向量化引擎,控制算子

官方技术文章 [了解详情](https://www.hikunpeng.com/zh/developer/techArticles)

openGauss数据库源码解析系列文章——执行器解析（三）

openGauss数据库源码解析系列文章——执行器解析（三）

实战经验

发表于 2023/04/21

0

## 向量化引擎

传统的行执行引擎大多采用一次一元组的执行模式，这样在执行过程中CPU大部分时间并没有用来处理数据，更多的是在遍历执行树，就会导致CPU的有效利用率较低。而在面对OLAP场景巨量的函数调用次数，需要巨大的开销。为了解决这一问题，openGauss中增加了向量化引擎。向量化引擎使用了一次一批元组的执行模式，能够大大减少遍历执行节点的开销。一次一批元组的数据运载方式也为某些表达式计算的SIMD（single instruction, multiple data，单指令多数据）化提供了机会，SIMD化能够带来性能上的提升。同时向量化引擎还天然对接列存储，能够较为方便地在底层扫描节点装填向量化的列数据。

向量化引擎的执行算子类似于行执行引擎，包含控制算子、扫描算子、物化算子和连接算子。同样会使用节点表示，继承于行执行节点，执行流程采用递归方式。主要包含的节点有：CStoreScan（顺序扫描），CStoreIndexScan（索引扫描），CStoreIndexHeapScan（利用Bitmap获取元组），VecMaterial（物化），VecSort（排序），VecHashJoin（向量化哈希连接）等，下面将逐一介绍这些执行算子。

### 1. 控制算子

（1）VecResult算子

VecResult算子用于处理只有一个结果返回或WHERE过滤条件为常量的情况，对应的代码源文件是“vecresult.cpp”；对应的主要数据结构是VecResult，VecResult继承于BaseResult。VecResult算子相关的函数包括ExecInitVecResult（初始化节点）、ExecVecResult（执行节点）、ExecReScanVecResult（重置节点）、ExecEndVecResult（退出节点）。

ExecInitVecResult函数用于初始化VecResult执行算子。执行流程如图一所示，主要执行流程如下。

a.  创建并初始化VecResult执行节点，并为节点创建表达式上下文。

b. 调用“ExecInitResultTupleSlot(estate, &res_state->ps)”函数分配存储投影结果的slot。

c.  调用投影表达式初始化函数ExecInitVecExpr依次对ps.targetlist、ps.qual和resconstantqual进行初始化。

d. 分别调用ExecAssignResultTypeFromTL函数和ExecAssignVectorForExprEval函数进行扫描描述符的初始化和投影结构的创建。

图28 ExecInitVecResult函数执行流程

ExecVecResult函数是执行VecResult的主体函数。执行流程如图29所示，主要执行流程如下。

a.  检查是否需要计算常量表达式。

b. 若需要则重新计算表达式，设置检查标识（如果常量计算表达式结果为false时，则设置约束检查标识位）。

c. 获取结果元组。

图29  ExecVecResult函数执行流程

ExecReScanVecResult函数用于重新执行扫描计划。

ExecEndVecResult函数用于在执行结束时释放执行过程中申请的相关资源（包括存储空间等）。

（2）VectorModifyTable算子

VecModifyTable算子用于处理INSERT、UPDATE、DELETE操作，对应的代码源文件是“vecmodifytable.cpp”；对应的主要数据结构是VecModifyTableState，VecModifyTableState继承于ModifyTableState。具体定义代码如下所示：

```
1  typedef struct VecModifyTableState : public ModifyTableState {
2      VectorBatch* m_pScanBatch;      /* 工作元组 */
3      VectorBatch* m_pCurrentBatch;  /* 输出元组 */
4  } VecModifyTableState;
```

VecModifyTable算子相关的函数包括ExecInitVecModifyTable（初始化节点）、ExecVecModifyTable（执行节点）、ExecEndVecModifyTable（退出节点）。

ExecInitVecModifyTable函数用于初始化VecModifyTable算子，调用ExecInitModifyTable函数实现算子的初始化。

ExecVecModifyTable函数是执行VecModifyTable算子的主体函数，循环地从子计划中获取目标列并根据要求修改每一列，通过“switch(operation)”处理不同的修改操作，具体的修改操作包括CMD_INSERT（插入）、CMD_DELETE（删除）、CMD_UPDATE（更新）。

ExecEndVecModifyTable函数用于在执行VecModifyTable算子结束时调用ExecEndModifyTable函数清除相关资源。

（3）VecAppend算子

VecAppend算子用于处理包含一个或多个子计划的链表，通过遍历子计划链表逐个执行子计划，对应的代码源文件是“vecappend.cpp”；对应的主要数据结构是VecAppendState，VecAppendState继承于AppendState。

VecAppend算子相关的函数包括ExecInitVecAppend（初始化节点）、ExecVecAppend（执行节点）、ExecReScanAppend（重置节点）、ExecEndVecAppend（退出节点）。

ExecInitVecAppend函数用于初始化VecAppend算子。执行执行流程如图30所示，主要执行流程如下。

a. 创建并初始化执行节点VecAppend。

b. 分配存储投影结果的slot。

c. 循环初始化子计划链表。

d. 初始化扫描描述符并设置初始迭代。

图30  ExecInitVecAppend函数执行流程

ExecVecAppend函数是执行VecAppend算子的主体函数。执行流程如图31所示，每次从子计划中获取一条元组，当取回全部元组时，移动到下一个子计划，直到执行全部子计划。

图31  ExecVecAppend执行流程

ExecEndVecAppend函数用于在执行结束时清理VecAppend算子，释放相应的子计划。

### 2.  扫描算子

（1）CStoreScan算子

CStoreScan算子用于扫描基础表，按顺序扫描基础表，对应的代码源文件是“veccstore.cpp”；CStoreScan算子对应的主要数据结构是CStoreScanState，CStoreScanState继承于ScanState。具体定义代码如下：

```
1  typedef struct CStoreScanState : ScanState {
2      Relation ss_currentDeltaRelation;
3      Relation ss_partition_parent;
4      TableScanDesc ss_currentDeltaScanDesc;
5      bool ss_deltaScan;
6      bool ss_deltaScanEnd;
7      VectorBatch* m_pScanBatch;
8      VectorBatch* m_pCurrentBatch;
9      CStoreScanRunTimeKeyInfo* m_pScanRunTimeKeys;
10      int m_ScanRunTimeKeysNum;
11      bool m_ScanRunTimeKeysReady;
12      CStore* m_CStore;
13      CStoreScanKey csss_ScanKeys;
14      int csss_NumScanKeys;
15      bool m_fSimpleMap;
16      bool m_fUseColumnRef;
17      vecqual_func jitted_vecqual;
18      bool m_isReplicaTable; /*复制表标记符*/
19  } CStoreScanState;
```

CStoreScan算子的相关函数包括：ExecInitCStoreScan（初始化节点）、ExecCStoreScan（执行节点）、ExecEndCStoreScan（退出节点）、ExecReScanCStoreScan（重置节点）。

ExecInitCStoreScan函数用于初始化CStoreScan算子。主要执行流程如下。

a. 创建并初始化CStoreScan算子，为节点创建表达式上下文。

b. 调用ExecAssignVectorForExprEval函数进行投影表达式的初始化。

c. 调用ExecInitResultTupleSlot函数和ExecInitScanTupleSlot函数分别初始化用于投影结果和用于扫描的slot。

d. 打开扫描表，调用ExecAssignResultTypeFromTL函数和ExecBuildVecProjectionInfo函数分别初始化结果扫描描述符和创建投影结构。

ExecCStoreScan函数是CStoreScan算子的主体函数，通过迭代的方式获取全部结果元组。

ExecEndCStoreScan函数用于在算子执行结束后清理CStoreScan算子。主要执行流程是：首先获取节点信息（包括Relation、ScanDesc），之后释放表达式上下文、元组，最后相应的partition、relation。

ExecReScanCStoreScan函数用于重新执行扫描计划。主要执行流程是：首先重置runtime关键词，当前节点partition信息，初始化接下来的partition信息，最后重置CStoreScan算子。

（2）CStoreIndexScan算子

CStoreIndexScan算子用于使用索引对表进行扫描，如果过滤条件中涉及索引，可以使用该算子加速元组获取，对应的代码源文件是“veccstoreindexscan.cpp”。CStoreIndexScan算子对应的主要数据结构是CStoreIndexScanState，CStoreIndexScanState继承于CStoreScanState。具体定义代码如下：

```
1  typedef struct CStoreIndexScanState : CStoreScanState {
2      CStoreScanState* m_indexScan;
3      CBTreeScanState* m_btreeIndexScan;
4      CBTreeOnlyScanState* m_btreeIndexOnlyScan;
5      List* m_deltaQual;
6      bool index_only_scan;
7      /* 扫描索引并从基表中得到以下信息 */
8      int* m_indexOutBaseTabAttr;
9      int* m_idxInTargetList;
10      int m_indexOutAttrNo;
11      cstoreIndexScanFunc m_cstoreIndexScanFunc;
12  } CStoreIndexScanState;
```

CStoreIndexScan算子的相关函数包括：ExecInitCStoreIndexScan（初始化节点）、ExecCStoreIndexScanT（执行节点）、ExecEndCStoreIndexScan（退出节点）、ExecReScanCStoreIndexScan（重置节点）。

ExecInitCStoreIndexScan函数用于初始化CStoreIndexScan算子。主要执行流程是：首先创建CStoreScan执行节点scanstate，之后根据scanstate创建CStoreIndexScanState节点。最后打开相关的relation和index。

ExecCStoreIndexScanT函数是CStoreIndexScan算子的主体函数。主要执行流程是：首先会从计划节点中获取Btree的相关信息，设置runtime扫描关键词，最后循环地获取结果集，直到执行结束。

ExecEndCStoreIndexScan函数用于在执行结束时清理CStoreIndexScan算子。主要执行流程是：首先清理相应的CStoreScan算子，之后相应的index、relation。

ExecReScanCStoreIndexScan函数用于重新执行扫描计划。主要执行流程是：首先重新扫描m_indexscan，之后重新扫描相关节点信息。

（3）CStoreIndexHeapScan算子

CStoreIndexHeapScan算子用于对属性上的索引进行扫描，返回结果为一个位图，其中标记了满足条件的元组在页面中的偏移量，对应的代码源文件是veccstoreindexheapscan.cpp；CStoreIndexHeapScan算子对应的主要数据结构是CStoreIndexHeapScanState，继承于CStoreIndexScanState。其中包含的核心函数有：ExecCstoreInitIndexHeapScan（初始化节点）、ExecCstoreIndexHeapScan（执行节点）、ExecReScanCstoreIndexHeapScan（重置节点）、ExecEndCstoreIndexHeapScan（退出节点）。

ExecCstoreInitIndexHeapScan函数是用于初始化CStoreIndexHeapScan算子。主要执行流程是：首先将计划节点转换为执行节点CStoreScan，之后复制CStoreScan算子、计划节点信息完成CStoreIndexHeapScan算子的初始化。

ExecCstoreIndexHeapScan函数是CStoreIndexHeapScan算子的主体函数。主要执行流程是：首先更新timing标记，之后迭代地获取目标迭代批次，直到获取全部结果。

ExecReScanCstoreIndexHeapScan函数用于重新执行扫描计划，通过调用VecExecReScan函数、ExecReScanCStoreScan函数实现重新扫描。

ExecEndCstoreIndexHeapScan函数用于在执行结束后清理CStoreIndexHeapScan算子占用的资源，通过调用ExecEndNode函数和ExecEndCStoreScan函数清理计划节点和执行节点。

（4）VecSubqueryScan算子

VecSubqueryScan算子将子计划作为扫描对象，实际执行中会转换为调用子节点计划，对应的代码源文件是vecsubqueryscan.cpp；VecSubqueryScan算子对应的主要数据结构是VecSubqueryScanState，继承于SubqueryScanState。包含的核心函数有：ExecInitVecSubqueryScan（初始化节点）、ExecVecSubqueryScan（执行节点）、ExecEndVecSubqueryScan（退出节点）、ExecReScanVecSubqueryScan（重置节点）。

ExecInitVecSubqueryScan函数是用于初始化VecSubqueryScan算子。主要执行流程是：首先初始化VecSubqueryScan执行算子，并为节点创建表达式上下文，接着初始化子查询计划，最后初始化元组和投影信息。

ExecVecSubqueryScan函数是VecSubqueryScan算子的主体函数。主要执行流程是：调用ExecVecScan执行算子，得到查询结果。

ExecReScanVecSubqueryScan函数用于重新执行扫描计划，通过调用ExecScanReScan函数重新扫描。

ExecEndVecSubqueryScan函数用于在执行结束后清理VecSubqueryScan算子占用的资源，通过调用ExecEndSubqueryScan函数进行清理。

（5）VecForeignScan算子

VecForeignScan算子对应的代码源文件是“vecforeignscan.cpp”。VecForeignScan算子对应的主要数据结构是VecForeignScanState，继承于ForeignScanState。该算子包含的核心函数有：ExecInitVecForeignScan（初始化节点）、ExecVecForeignScan（执行节点）、ExecEndVecForeignScan（退出节点）、ExecReScanVecForeignScan（重置节点）。

ExecInitVecForeignScan函数是用于初始化VecForeignScan算子。主要执行流程如下。

a.  创建VecForeignScanState执行节点。

b. 设置表达式上下文。

c. 调用ExecInitVecExpr函数依次为“ss.ps.targetlist”和“ss.ps.qual”初始化表达式。

d. 调用ExecBuildVecProjectionInfo函数创建投影结构。

ExecVecForeignScan函数是VecForeignScan算子的主体函数，通过调用ExecVecScan执行算子，得到查询结果。

ExecReScanVecForeignScan函数用于重新执行扫描计划，通过调用ExecScanReScan函数实现重新扫描。

ExecEndVecForeignScan函数用于在执行结束后清理VecForeignScan算子占用的资源，通过调用MemoryContextDelete函数清除上下文和ExecEndForeignScan函数清除执行节点。

### 3.  物化算子

（1）VecMaterial算子

VecMaterial算子能够缓存需要多次重复扫描的子节点结果，有助于减少执行中的扫描代价。

VecMaterial算子对应的代码源文件是“vecmaterial.cpp”。VecMaterial算子对应的主要数据结构是VecMaterialState，继承于MaterialState。相关代码如下：

```
1  typedef struct VecMaterialState : public MaterialState {
2      VectorBatch* m_pCurrentBatch;
3      BatchStore* batchstorestate;
4      bool from_memory;
5  } VecMaterialState;
```

VecMaterial算子的相关函数包括：ExecInitVecMaterial（初始化节点）、ExecVecMaterial（执行节点）、ExecEndVecMaterial（退出节点）、ExecReScanVecMaterial（重置节点）。

ExecInitVecMaterial函数用于初始化VecMaterial算子。主要执行流程如下。

a.  创建并初始化VecMaterialState执行节点。

b. 分别调用“ExecInitResultTupleSlot(estate, &matstate->ss.ps)”函数和“ExecInitScanTupleSlot(estate, &matstate->ss)”函数分配用于存储投影结果和用于扫描的slot。

c. 调用“ExecAssignScanTypeFromOuterPlan(&matstate->ss)”函数初始化元组类型，调用“ExecAssignResultTypeFromTL(&matstate->ss.ps,matstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor->tdTableAmType)”函数初始化结果扫描描述符。

d. 最后如果当前VecMaterial节点处于子计划中并且物化Stream数据，需要将其添加到estate->es_material_of_subplan中。

ExecVecMaterial函数是VecMaterial算子的主体函数，根据materalAll判断是否需要一次性物化所有元组，通过分别调用exec_vec_material_all函数、exec_vec_material_one函数完成算子的执行。其中exec_vec_material_all函数会一次性物化全部元组，之后根据需要返回部分元组，而exec_vec_material_one函数则会逐个物化元组。

ExecEndVecMaterial函数用于清理VecMaterial算子执行过程中使用的资源主要执行流程是：首先调用“ExecClearTuple(node->ss.ss_ScanTupleSlot)”函数清理tuple table，之后调用batchstore_end释放用于存储元组的资源，最后调用“ExecEndNode(outerPlanState(node)) ”函数清理子计划节点。

ExecReScanVecMaterial函数用于重新执行扫描计划，流程如图32所示。主要执行流程是：

a. 根据eflags判断tuplestore是否进行其他操作（如REWIND、BACKWARD、RESTORE）。

b. 若未进行其他操作，则直接调用“VecExecReScan(node->ss.ps.lefttree)”函数进行重新扫描，反之则需要进一步判断是否已执行了物化操作。

c. 若未进行物化操作，则同样直接调用“VecExecReScan(node->ss.ps.lefttree)”函数进行重新扫描；若已产生物化结果，则需要根据进行操作的不同进行不同处理。

d. 已产生物化结果可以分为2种情况：REWIND操作和其他操作。如果进行了REWIND操作，则需要调用batchstore_end函数释放已经存储的结果，之后调用“VecExecReScan(node->ss.ps.lefttree)”函数重新扫描。对于其他操作，需要判断当前计划是否为partition-wise join并且是否需要切换分区。

e. 如是则同样需要调用batchstore_end和VecExecReScan函数重新扫描计划；反之只需要调用“batchstore_rescan(node->batchstorestate)”函数。

图32  ExecReScanVecMaterial函数执行流程

（2）VecSort算子

VecSort算子用于缓存下层节点返回的所有结果元组并进行排序，对于结果元组较多时，会使用临时文件进行存储，并使用外排序进行排序操作。

VecSort算子对应的代码源文件是“vecsort.cpp”，VecSort算子对应的主要数据结构是VecSortState，继承于SortState。相应代码如下：

```
1  typedef struct VecSortState : public SortState {
2      VectorBatch* m_pCurrentBatch;
3  char* jitted_CompareMultiColumn;         
4  char* jitted_CompareMultiColumn_TOPN; 
5  } VecSortState;
```

VecSort算子的相应函数有：ExecInitVecSort（初始化节点）、ExecVecSort（执行节点）、ExecEndVecSort（退出节点）、ExecReScanVecSort（重置节点）。

ExecInitVecSort函数用于初始化VecSort算子。主要执行流程如下。

a. 创建并初始化VecSortState执行节点。

b. 分别调用“ExecInitResultTupleSlot(estate, &sort_state->ss.ps)”函数用于存储投影结果和“ExecInitScanTupleSlot(estate, &sort_state->ss)”函数用于初始化扫描元组槽。

c. 调用“ExecAssignScanTypeFromOuterPlan(&sort_state->ss)”函数初始化元组类型，调用“ExecAssignResultTypeFromTL(&sort_stat->ss.ps,sort_stat->ss.ss_ScanTupleSlot->tts_tupleDescriptor->tdTableAmType)”函数初始化结果扫描描述符。

ExecVecSort函数是VecSort算子的主体函数。主要执行流程是：初次执行时，首先调用batchsort_begin_heap函数初始化元组缓存结构，之后循环执行从下层节点获取元组；调用sort_putbatch将获取的元组存放到缓存中；获取全部元组之后，调用batchsort_performsort进行排序；后续对VecSort算子的执行，调用batchsort_getbatch直接从缓存中获取一个元组。

ExecEndVecSort函数用于清理VecSort算子执行过程中使用的资源。主要执行流程是：首先调用ExecClearTuple函数依次清理“node->ss.ss_ScanTupleSlot”元组缓存和“node->ss.ps_ResultTupleSlot”排序后的元组缓存，之后调用batchsort_end函数释放用于元组排序的资源，最后调用“ExecEndNode(outerPlanState(node)) ”函数清理子计划节点。

ExecReScanVecSorts函数用于重新执行扫描计划，执行流程图如图33所示。主要执行流程是：

a. 判断是否已经进行过排序；如没有执行过，则调用VecExecReScan函数重新扫描执行节点即可，反之则先判断子节点是否已经重新扫描。

b. 如果子节点已经重新扫描，则调用ExecClearTuple函数清理已经排序的结果元组，调用batchsort_end函数清理“node->tuplesortstate”，调用VecExecReScan函数重新扫描执行节点；如果没有，则判断当前计划是否是“partition-wise join”并且需要切换分区。

c. 如果当前计划是“partition-wise join”并且需要切换分区，则同样需要调用batchstore_end函数和VecExecReScan函数进行重新扫描计划；否则只需要调用“batchstore_rescan(node->tuplesortstate)”。

ExecReScanVecSorts函数执行流程如图33所示。

图33  ExecReScanVecSort函数执行流程

（3）VecLimit算子

VecLimit算子用于处理Limit子句，对应的代码源文件是“veclimit.cpp”。VecLimit算子对应的主要数据结构是VecLimitState，VecLimitState继承于LimitState。具体定义代码如下：

```
1  struct VecLimitState : public LimitState {
2      VectorBatch* subBatch;
3  };
```

VecLimit算子的相关函数包括ExecInitVecLimit（初始化节点）、ExecVecLimit（执行节点）、ExecReScanVecLimit（重置节点）、ExecEndVecLimit（退出节点）。

ExecInitVecLimit函数用于初始化VecLimit算子，将VecLimit计划节点转换为VecLimit执行节点。主要执行流程如下。

a. 创建VecLimit执行节点，创建表达式上下文，分别初始化limitOffset（调用“ExecInitExpr((Expr)node->limitOffset, (PlanState)limit_state)）” 函数和limitCount（调用“ExecInitExpr((Expr)node->limitCount, (PlanState)limit_state)”函数）表达式。

b. 调用“ExecInitResultTupleSlot(estate, &limit_state->ps) ”函数进行初始化元组。

c. 调用“ExecInitNode(outer_plan, estate, eflags) ”函数进行初始化外部计划。最后置空投影结构。

ExecVecLimit函数是VecLimit算子的主体函数。函数中通过switch来处理VecLimit算子中存在的多种状态，node->Istate存在的状态有LIMIT_INITIAL、LIMIT_RESCAN、LIMIT_EMPTY、LIMIT_INWINDOW、LIMIT_SUBPLANEOF、LIMIT_WINDOWEND、LIMIT_WINDOWSTART。其中LIMIT_INITIAL表示处理Limit算子初始化，LIMIT_RESCAN表示重新执行子节点计划，LIMIT_EMPTY表示Limit算子是空集，LIMIT_INWINDOW表示处理窗口函数（在窗口函数内前向和后向移动），LIMIT_SUBPLANEOF表示处理子节点计划（移动到子节点计划尾部），LIMIT_WINDOWEND表示在窗口结尾部分结束，LIMIT_WINDOWSTART表示在窗口开始部分结束。

ExecEndVecLimit函数用于在执行VecLimit算子结束时释放相关资源，通过依次调用“ExecFreeExprContext(&node->ps)”函数和“ExecEndNode(outerPlanState(node))”函数释放表达式上下文和节点相关信息。

ExecReScanVecLimit函数用于重新执行扫描计划。在参数发生改变时，通过调用“recompute_limits(node)”函数完成执行节点的重新扫描和VecLimit状态机的重置。在chgParam为空时，还需要调用VecExecReScan函数重新扫描计划节点。

（4）VecGroup算子

VecGroup算子用于处理SQL语句中的“GROUP BY”子句，对满足条件的元组做分组处理。

VecGroup算子对应的代码源文件是“vecgroup.cpp”。VecGroup算子对应的主要数据结构是VecGroupState，继承于GroupState。相关代码如下：

```
1  struct VecGroupState : public GroupState {
2      void** container;
3      void* cap;
4      uint16 idx;
5      int cellSize;
6      bool keySimple;
7      FmgrInfo* buildFunc;
8      FmgrInfo* buildScanFunc;
9      VectorBatch* scanBatch;
10      VarBuf* currentBuf;
11      VarBuf* bckBuf;
12      vecqual_func jitted_vecqual; 
13  };
```

VecGroup算子的相应函数有：ExecInitVecGroup（初始化节点）、ExecVecGroup（执行节点）、ExecEndVecGroup（退出节点）、ExecReScanVecGroup（重置节点）。

ExecInitVecGroup函数用于初始化VecGroup算子，主要执行流程如下。

a. 创建并初始化VecGroupState执行节点，并为节点创建表达式上下文。

b. 调用“ExecInitResultTupleSlot(estate,&grp_state->ss.ps);”函数分配存储投影结果的slot。

c. 调用投影表达式初始化函数ExecInitVecExpr依次对plan.targetlist和plan.qual进行初始化。

d. 调用ExecInitNode函数初始化子节点。

e. 调用ExecAssignResultTypeFromTL函数初始化结果扫描描述符和调用ExecAssignVectorForExprEval函数创建投影结构。

ExecVecGroup函数是VecGroup算子的主体函数。主要执行流程如下。

a. 获取下层元组中符合having子句条件的第1个元组。

b. 依次获取组内的所有元组，直到获取到分组属性不同的元组，此时表示当前分组获取结束；如果获取到空元组，则表示完成分组操作，设置grp_done字段为true并结束执行。

c. 扫描下一个符合having条件的元组，将缓存的元组作为分组的开始，并返回新元组。

d. 重复b、c直到结束。

ExecEndVecGroup函数用于清理VecGroup算子执行过程中使用的资源。主要执行流程是：首先调用ExecFreeExprContext函数清理表达式上下文，最后调用“ExecEndNode(outerPlanState(node))”函数清理子计划节点。

ExecReScanVecGroup函数用于重新执行扫描计划，通过调用VecExecReScan函数实现重新扫描。

（5）VecAggregation算子

VecAggregation算子用于处理含有聚集函数的操作，将同一分组下的多个元组合并成一个聚集结果元组。

VecAggregation算子对应的代码源文件是“vecagg.cpp”。VecAggregation算子对应的主要数据结构是VecAggState，继承于AggState。相应代码如下：

```
1  typedef struct VecAggState : public AggState {
2      void* aggRun;
3      VecAggInfo* aggInfo;
4      char* jitted_hashing;
5      char* jitted_sglhashing;
6      char* jitted_batchagg;
7      char* jitted_sonicbatchagg;
8      char* jitted_SortAggMatchKey;
9  } VecAggState;
```

VecAggregation算子对应的核心函数有：ExecInitVecAggregation（初始化节点）、ExecVecAggregation（执行节点）、ExecEndVecAggregation（退出节点）、ExecReScanVecAggregation（重置节点）。

ExecInitVecAggregation函数用于初始化VecAggregation算子。主要执行流程如下。

a. 创建并初始化VecAggState执行节点，并调用ExecAssignExprContext函数为节点创建表达式上下文。

b. 调用ExecInitScanTupleSlot函数分配用于扫描的slot，调用ExecInitResultTupleSlot函数分配存储投影结果的slot，调用ExecInitExtraTupleSlot函数为sort_slot进行初始化。

c. 调用投影表达式初始化函数ExecInitVecExpr依次对“plan.targetlist”和“plan.qual”进行初始化。

d. 调用ExecInitNode函数初始化子节点，获取其中的Aggref节点。

e. 使用每个Aggref节点中包含的聚集函数信息进行初始化，构造出对应的AggStatePerAgg。

f. 最后根据策略类型，初始化相应的状态信息。

ExecVecAggregation函数是VecAggregation算子的主体函数。根据策略类型的不同（hash、plain、sort），调用不同的Runner函数执行。

ExecEndVecAggregation函数用于清理VecAggregation算子执行过程中使用的资源。主要执行流程是：依据选择的策略Hash、sort、plain分别调用freeMemoryContext函数、endSortAgg函数、endPlainAgg函数清理节点信息，之后分别调用ExecFreeExprContext函数和ExecClearTuple函数对表达式上下文和元组缓存进行清理。

ExecReScanVecAggregation函数用于重新执行扫描计划。主要执行流程是：根据策略类型分别调用相应的ResetNecessary函数重置相应执行节点，最后调用VecExecReScan函数实现重新扫描。

（6）VecWindowAgg算子

VecWindowAgg算子用于处理窗口函数的聚集操作。不同于Agg算子，窗口函数不会将同一分组中的元组合并为一个，这样就需要对每个元组都产生一个结果元组，其中包含对应的聚集计算结果。

VecWindowAgg算子对应的代码源文件是“vecwindowagg.cpp”。VecWindowAgg算子对应的主要数据结构是VecWindowAggState，继承于WindowAggState。相关代码如下：

```
1  typedef struct VecWindowAggState : public WindowAggState {
2      void* VecWinAggRuntime;
3      VecAggInfo* windowAggInfo;
4  } VecWindowAggState;
```

VecWindowAgg算子中对应的核心函数有：ExecInitVecWindowAgg（初始化节点）、ExecVecWindowAgg（执行节点）、ExecEndVecWindowAgg（退出节点）、ExecReScanVecWindowAgg（重置节点）。

ExecInitVecWindowAgg函数用于初始化VecWindowAgg算子，主要执行流程如下。

a. 创建并初始化VecWindowAgg执行节点，并调用ExecAssignExprContext函数为节点创建表达式上下文。

b. 调用ExecInitResultTupleSlot函数分配存储投影结果的slot，调用ExecInitScanTupleSlot函数分配用于扫描的slot。

c. 调用ExecInitVecExpr函数为ps.targetlist初始化投影表达式。

d. 初始化分区判断函数和排序属性是否相同的操作函数，保存在partEqfunctions、ordEqfunctions中。

e. 初始化funcs指向的表达式树，构造相关调用信息并存放在perfunc中。

ExecVecWindowAgg函数是VecWindowAgg算子的主体函数，通过调用getBatch执行算子，得到窗口函数的投影结果。

ExecEndVecWindowAgg函数用于清理VecWindowAgg算子执行过程中使用的资源，通过调用batchstore_end函数清理元组缓存，通过调用ExecEndNode函数清理执行节点。

ExecReScanVecWindowAgg函数用于重新执行扫描计划，通过调用ResetNecessary函数重置相应执行节点，通过调用VecExecReScan函数实现重新扫描。

（7）VecSetOp算子

VecSetOp算子用于处理EXECEPT和INTERSECT集合操作。一般一个VecSetOp算子中只能处理两个集合之间的集合操作，对于多个集合之间的集合操作，需要多个SetOp实现。

VecSetOp算子对应的代码源文件是“vecsetop.cpp”。VecSetOp算子对应的主要数据结构是VecSetOpState，继承于SetOpState。相关代码如下：

```
1  typedef struct VecSetOpState : public SetOpState {
2      void* vecSetOpInfo;
3  } VecSetOpState;
```

VecSetOp算子中对应的核心函数有：ExecInitVecSetOp（初始化节点）、ExecVecSetOp（执行节点）、ExecEndVecSetOp（退出节点）、ExecReScanVecSetOp（重置节点）。

ExecInitVecSetOp函数用于初始化VecSetOp算子。主要执行流程如下。

a. 创建并初始化VecSetOpState执行节点。

b. 调用ExecInitResultTupleSlot函数分配存储投影结果的slot。

c. 调用ExecInitnode函数初始化子节点。

d. 调用ExecAssignResultTypeFromTL函数初始化结果扫描描述符。

ExecVecSetOp函数是VecSetOp算子的主体函数，通过执行VecSetOp算子状态机，产生resultBatch。

ExecEndVecSetOp函数用于清理VecSetOp算子执行过程中使用的资源。通过调用freeMemoryContext函数释放内存上下文，通过调用ExecClearTuple函数清理元组缓存，通过调用ExecEndNode函数清理执行节点。

ExecReScanVecSetOp函数用于重新执行扫描计划，通过调用ExecClearTuple函数清理元组结果缓存，通过调用ResetNecessary函数重置相应执行节点。

### 4.  连接算子

（1）VecNestLoop算子

VecNestLoop算子对应的主要数据结构是VecNestLoopState，VecNestLoopState继承于NestLoopState。具体定义代码如下：

```
1  struct VecNestLoopState : public NestLoopState {
2      void* vecNestLoopRuntime;
3      vecqual_func jitted_vecqual;
4      vecqual_func jitted_joinqual;
5  };
```

VecNestLoop算子的相关函数包括：ExecInitVecNestLoop（初始化节点）、ExecVecNestLoop（执行节点）、ExecEndVecNestLoop（退出节点）、ExecReScanVecNestLoop（重置节点）。

ExecInitVecNestLoop函数用于初始化VecNestLoop执行算子。主要执行流程如下。

a. 初始化VecNestLoop执行算子。

b. 为节点创建表达式上下文，分别处理左右子树，得到外执行计划节点和内执行计划节点。

c. 初始化元组和投影信息。

ExecVecNestLoop函数是执行VecNestLoop的主体函数，通过执行VecNestLoop状态机，并获得结果元组。

ExecEndVecNestLoop函数用于在执行结束时清理VecNestLoop算子。主要执行流程是：首先释放表达式上下文，之后清空元组，最后清空子计划节点。

ExecReScanVecNestLoop函数用于重新执行扫描计划。主要执行流程是：首先把VecNestLoop计划节点转换成外计划执行节点，之后判断外计划执行节点的chgParam是否为空，若chgParam为空，则重新扫描节点。

（2）VecMergeJoin算子

VecMergeJoin算子对应的主要数据结构是VecMergeJoinState，VecMergeJoinState继承于MergeJoinState。具体定义代码如下：

```
1  struct VecMergeJoinState : public MergeJoinShared {
2      /* 向量化执行支持 */
3      VecMergeJoinClause mj_Clauses;
4      MJBatchOffset mj_OuterOffset;
5      MJBatchOffset mj_InnerOffset;
6      ExprContext* mj_OuterEContext;
7      ExprContext* mj_InnerEContext;
8      MJBatchOffset mj_MarkedOffset;
9      VectorBatch* mj_MarkedBatch;
10      BatchAccessor m_inputs[2];
11      MJBatchOffset m_prevInnerOffset;
12      bool m_prevInnerQualified;
13      MJBatchOffset m_prevOuterOffset;
14      bool m_prevOuterQualified;
15      bool m_fDone;
16      VectorBatch* m_pInnerMatch;
17      MJBatchOffset* m_pInnerOffset;
18      VectorBatch* m_pOuterMatch;
19      MJBatchOffset* m_pOuterOffset;
20      VectorBatch* m_pCurrentBatch;
21      VectorBatch* m_pReturnBatch;
22  vecqual_func jitted_joinqual; 
23  };
```

VecMergeJoin算子的相关函数包括：ExecInitVecMergeJoin（初始化节点）、ExecVecMergeJoinT（执行节点）、ExecEndVecMergeJoin（退出节点）、ExecReScanVecMergeJoin（重置节点）。

ExecInitVecMergeJoin函数用于初始化VecMergeJoin执行算子。主要执行流程如下。

a. 初始化VecMergeJoin执行算子。

b. 为节点创建表达式上下文，分别处理左右子树，得到外执行计划节点和内执行计划节点。

c. 初始化元组和投影信息。

ExecVecMergeJoinT函数是执行VecMergeJoin的主体函数，执行VecMergeJoin状态机，并根据join类型，获取结果元组。

ExecEndVecMergeJoin函数用于在执行结束时清理VecMergeJoin算子。首先释放表达式上下文，之后清空元组，最后清空左右子树节点。

ExecReScanVecMergeJoin函数用于重新执行扫描计划。主要执行流程是：首先重置节点相关参数，之后判断左右子树的chgParam是否为空；若chgParam为空时，则重新扫描节点。

（3）VecHashJoin算子

VecHashJoin算子对应的主要数据结构是VecHashJoinState，VecHashJoinState继承于HashJoinState。具体定义如下：

```
1  typedef struct VecHashJoinState : public HashJoinState {
2      int joinState;
3      void* hashTbl;
4      FmgrInfo* eqfunctions;
5  vecqual_func jitted_joinqual;
6  vecqual_func jitted_hashclause; 
7      char* jitted_innerjoin;
8      char* jitted_matchkey;
9      char* jitted_buildHashTable;
10      char* jitted_probeHashTable;
11      int enable_fast_keyMatch;
12      BloomFilterRuntime bf_runtime;
13      char* jitted_hashjoin_bfaddLong;
14      char* jitted_hashjoin_bfincLong;
15      char* jitted_buildHashTable_NeedCopy;
16  } VecHashJoinState;
```

VecHashJoin算子的相关函数包括：ExecInitVecHashJoin（初始化节点）、ExecVecHashJoin（执行节点）、ExecEndVecHashJoin（退出节点）、ExecReScanVecHashJoin（重置节点）。

ExecInitVecHashJoin函数用于初始化Vechash join执行算子，并把VecHashJoin计划节点转换成计划执行节点。主要执行流程是：首先处理左子树，得到外执行计划节点；再处理右子树，得到内执行计划节点；最后初始化元组和投影信息。

ExecVecHashJoin函数是执行VecHashJoin的主体函数，执行VecHashJoin状态机。

ExecEndVecHashJoin函数用于在执行结束时清理VecHashJoin算子。主要执行流程是：首先释放内存上下文，之后释放表达式，清空左右子树。流程如图34所示。

图34  ExecEndVecHashJoin函数执行流程

ExecReScanVecHashJoin函数用于重新执行扫描计划。主要执行流程是：首先判断状态信息，如哈希表为空时，只需要重新扫描左子树计划，否则需要重新构建哈希表。

## 小结

本章节主要介绍了执行器的总体框架、执行器算子、向量化引擎；向量化引擎通过编译执行模块实现执行加速。执行器接收Plan（优化器输出），对Plan做转换处理，生成状态树，状态树的节点对应执行算子（这些算子利用存储和索引提供的接口，实现数据读写）；执行器是SQL语句同存储交互的中介。这些执行算子有统一的接口以及相似的执行流程（初始化、迭代执行、清理3个过程）。向量化引擎面向OLAP场景需求，同编译执行相结合提供高效执行效率。

往期回顾：

openGauss数据库源码解析系列文章——执行器解析（二） [了解详情](https://www.hikunpeng.com/zh/developer/techArticles/20230906-7?envFlag=1)

openGauss数据库源码解析系列文章——执行器解析（一） [了解详情](https://www.hikunpeng.com/zh/developer/techArticles/20230907-1?envFlag=1)

本页内容
