opengauss数据库源码解析系列文章——执行器解析(代码片段)

Gauss松鼠会 Gauss松鼠会     2022-12-22     287

关键词:

本篇我们开启第七章执行器解析中“7.1 执行器整体架构及代码概览”、“7.2 执行流程”及“7.3 执行算子”的相关精彩内容介绍。

执行器在数据库整个体系结构中起到承上启下的作用,对上承接优化器产生的最优执行计划,并按照执行计划进行流水线式的执行,对底层的存储引擎中的数据进行操作。openGauss数据库将执行的过程抽象成了不同类型的算子,同时结合编译执行、向量化执行、并行执行等方式,组成了全面、高效的执行引擎。本章着重介绍执行器的整体架构、执行模型、各类算子、表达式,以及编译执行和向量化引擎等全新的执行引擎。

7.1 执行器整体架构及代码概览

本节整体介绍执行器的架构和代码。

7.1.1 执行器整体结构

在SQL引擎将用户的查询解析优化成可执行的计划之后,数据库进入查询执行阶段。执行器基于执行计划对相关数据进行提取、运算、更新、删除等操作,以达到用户查询想要实现的目的。
openGauss在行计算引擎的基础上,增加了编译执行引擎和向量化执行引擎,执行器模块架构如图7-1所示。openGauss的执行器采用的是火山模型(volcano model),这是一种经典的流式迭代模型(pipeline iterator model),目前主流的关系型数据库大多采用这种执行模型。

图7-1 执行器模块架构

执行器包括四个主要的子模块:Portal、ProcessUtility、executor和特定功能子模块。首先在Portal模块中根据优化器的解析结果,选择相应的处理策略和处理模块(ProcessUtility和executor)。其中executor主要处理用户的增删改查等DML(Data Manipulation Language,数据操作语言)操作。然后ProcessUtility处理增删改查之外的其他各种情况,例如各类DDL(data definition language,数据定义语言)语句、游标操作、事务相关操作、表空间操作等。

7.1.2 火山模型

执行器(executor)的输入是优化器产生的计划树(plan tree),计划树经过执行器转换成执行状态树。执行状态树的每一个节点对应一个独立算子,每个算子都完成一项单一功能,所有算子组合起来,实现了用户的查询目标。在火山模型中,多个算子组成了一个由一个根节点、多个叶子节点和多个中间节点组成的查询树。
每个算子有统一的接口(迭代器模式),从下层的一个或者多个算子获得输入,然后将运算结果返回给上层算子。整个查询执行过程主要是两个流,驱动流和数据流。
驱动流是指上层算子驱动下层算子执行的过程,这是一个从上至下、由根节点到叶节点的过程,如图7-2中的向下的箭头所示。从代码层面来看,即上层算子会根据需要调用下层算子的函数接口,去获取下层算子的输入。驱动流是从跟节点逐层传递到叶子节点。
数据流是指下层算子将数据返回给上层算子的过程,这是一个从下至上,从叶节点到跟节点的过程,如图7-2中的向上的箭头所示。在openGauss中,所有的叶子节点都是都是表数据扫描算子,这些节点是所有计算的数据源头。数据从叶子节点,通过逐层计算,然后从根节点返回给用户。

图7-2 执行器控制流和数据流示意图

7.1.3 代码概览

执行器在项目中的源代码路径为:src/gausskernel/runtime。下面是执行器的源码目录。
1) 执行器源码目录

执行器源码目录如表7-1所示。

表7-1 执行器源码目录

模块

功能

Makefile

编译脚本

codegen

计划编译,加速热点代码执行

executor

执行器核心模块,包括表达式计算、数据定义处理以及行级执行算子

vecexecutor

向量化执行引擎

2) 执行器源码文件

执行器源码目录为:src/gausskernel/runtime/模块名。文件如表7-2所示。

表7-2 执行器源码文件

模块名

源码文件

功能

codegen

codegenutil

编译执行辅助工具

executor

执行器

llvmir

llvm表达式生成

vecexecutor

向量化引擎

Makefile

编译配置文件

executor

Makefile

编译配置文件

execAmi.cpp

执行器路由算子

execCurrent.cpp

节点控制

execGrouping.cpp

支持分组、哈希和聚集操作

execJunk.cpp

伪列的支持

execMain.cpp

顶层执行器接口

execMerge.cpp

处理MERGE指令

execProcnode.cpp

分发函数按节点调用相关初始化等函数

execQual.cpp

评估资质和目标列表的表达式

execScan.cpp

通用的关系扫描

execTuples.cpp

元组相关的资源管理

execUtils.cpp

多种执行相关工具函数

functions.cpp

执行SQL语言函数

instrument.cpp

计划执行工具

lightProxy.cpp

轻量级执行代理

nodeAgg.cpp

聚合算子

nodeAppend.cpp

添加算子

nodeBitmapAnd.cpp

位图与算子

nodeBitmapHeapsScan.cpp

位图堆扫描算子

nodeBitmapIndexScan.cpp

位图扫描算子

nodeBitmapOr.cpp

位图或算子

nodeCtescan.cpp

通用表达式扫描算子

...

...

README

说明文件

vecnode/vecagg.cpp

向量聚合算子

vecnode/vecappend.cpp

向量添加算子

vecnode/vecconstraints.cpp

约束检查

vecnode/veccstore.cpp

列存扫描算子

vecnode/veccstoreindexand.cpp

列存索引扫描算子

vecnode/veccstoreindextidscan.cpp

列存tid扫描算子

...

...

vecnode/Readme.md

说明文件

vecprimitive/date.inl

基础数据类型

vecprimitive/float.inl

浮点数据类型

vecprimitive/int4.inl

4字节整数类型

vecprimitive/int8.inl

8字节整数类型

vecprimitive/numeric.inl

数值类型

...

...

vecprimitive/Readme.md

说明文件

vectorsonic/vsonicfilesource.cpp

读写加速

vectorsonic/vsonicchash.cpp

Hash加速

vectorsonic/vsonichashagg.cpp

Hash聚合

vectorsonic/vsonichashjoin.cpp

Hash连接

7.2 执行流程

整个执行器的执行流程主要包括了策略选择模块Portal、执行组件executor和ProcessUtility,如图7-3所示。下面逐个进行介绍。

图7-3 执行器总体执行流程

7.2.1 Portal策略选择模块

Portal是执行SQL语句的载体,每一条SQL对应唯一的Portal,不同的查询类型对应的Portal类型也有区别,如表7-3所示。SQL语句经过查询编译器处理后会生成优化计划树或非优化计划树,是执行器执行的“原子”操作,执行策略根据需要选择SQL的类型、调用相应的模块。Portal的生命周期管理在exec_simple_query函数中实现,该函数负责Portal创建、执行和清理。Portal执行的主要执行流程包括PortalStart函数、PortalRun函数、PortalDrop函数几个部分。其中PortalStart函数负责进行Portal结构体初始化工作,包括执行算子初始化、内存上下文分配等;PortalRun函数负责真正的执行和运算,它是执行器的核心;PortalDrop函数负责最后的清理工作,主要是数据结构、缓存的清理。

表7-3 Portal类型

Portal类别

SQL类型

PORTAL_ONE_SELECT

SQL语句包含单一的SELECT查询

PORTAL_ONE_RETURNING

INSERT/UPDATE/DELETE语句包含Returning

PORTAL_ONE_MOD_WITH

查询语句包含With

PORTAL_UTIL_SELECT

工具类型查询语句,如explain

PORTAL_MULTI_QUERY

所有其他类型查询语句

数据库中的查询主要分为两大类,DDL(CREATE、DROP、ALTER等)查询和DML(SELECT、INSERT、UPDATE、DELETE)查询。这两类查询在执行器中的执行路径存在一定的差异。下面分别介绍这两类查询的主要函数调用关系。

7.2.2 ProcessUtility模块

除了DML之外的所有查询,都通过ProcessUtility模块来执行,包括了各类DDL语句、事务相关语句、游标相关语句等。
DDL查询的上层函数调用为exec_simple_query函数,其中PortalStart函数和PortalDrop函数部分较为简单。核心函数是PortalRun函数下层调用的standard_ProcessUtility函数,该函数通过switch case语句处理了各种类型的查询语句,调用关系如图7-4所示。包括事务相关查询、游标相关查询、schema相关操作、表空间相关操作、表定义相关操作等。

图7-4 DDL查询主要函数调用关系

7.2.3 executor模块

DML会被优化器解析并产生成计划树。在执行阶段的上层函数调用和DDL类似,也是exec_simple_query函数。其中PortalStart函数会遍历整个查询计划树,对每个算子进行初始化。算子初始化函数的命名一般是“ExecInit+算子名”的形式,通过这种方式可以方便的查找到对应算子的初始化函数。初始化函数中首先会根据对应的Plan结构初始化一个对应的PlanState结构,这个结构是执行过程中的核心数据结构,包含了在执行过程中需要用的一些数据存储空间以及执行信息。
在PortalRun函数中会实际执行相关的DML查询,对数据进行计算和处理。在执行过程中,所有执行算子分为2大类,行存储算子和向量化算子。这两类算子分别对应行存储执行引擎和向量化执行引擎。行存储执行引擎的上层入口是ExecutePlan函数,向量化执行引擎的上层入口是ExecuteVectorizedPlan函数。其中向量化引擎是针对列存储表的执行引擎,会在7.6小节详细介绍。如果存在行存储表和列存储表的混合计算,那么行存储执行引擎和向量化执行引擎直接可以通过VecToRow和RowToVec算子进行相互转换。行存储算子执行入口函数的命名规则一般为“Exec+算子名”的形式,向量化算子执行入口函数的命名规则一般为“ExecVec+算子名”的形式;通过这样的命名规则,可以快速地找到对应算子的函数入口。
在PortalDrop函数中会调用ExecEndPlan函数对各个算子进行递归清理,主要是清理在执行过程中产生的内存。各个算子的清理函数入口命名规则是“ExecEnd+算子名”以及“ExecEndVec+算子名”,调用关系如图7-5所示。

图7-5 DML查询主要函数调用关系

7.3 执行算子

执行算子模块包含多种计划执行算子,算子类型如表7-4所示,是计划执行的独立单元,用于实现具体的计划动作。执行计划包含4类算子,分别是控制算子、扫描算子、物化算子和连接算子,如表7-4所示。这些算子统一使用节点(node)表示,具有统一的接口,执行流程采用递归模式。整体执行流程是:首先根据计划节点的类型初始化状态节点(函数名为“ExecInit+算子名”),然后再回调执行函数(函数名为“Exec+算子名”),最后是清理状态节点(函数名为“ExecEnd+算子名”)。本节主要介绍行执行算子,面向列存储的算子在后续章节(向量化引擎)介绍。

表7-4 执行算子类型

算子类型

说明

控制算子

处理特殊执行流程,如Union语句

扫描算子

用于扫描表对象,从表中获取数据

物化算子

缓存中间执行结果到临时存储

连接算子

用于实现SQL中的各类join操作,通常包含nested loop join、hash join、merge-sort join等

7.3.1 控制算子

控制算子主要用于执行特殊流程,这类流程通常包含两个以上输入,如Union操作,需要把多个子结果(输入),合并成一个。控制算子有多种,如表7-5所示。

表7-5 控制算子

算子名称

说明

Result算子

处理只有一个结果或过滤条件是常量

Append算子

处理包含一个或多个子计划的链表

BitmapAnd算子

对结果做And位图运算

BitmapOr算子

对结果做Or位图运算

RecursionUnion算子

递归处理UNION语句

1. Result算子

Result算子对应的代码源文件是“nodeResult.cpp”,用于处理只有一个结果(如通过SELECT调用可执行函数或表达式,或者INSERT语句只包含Values字句)或者WHERE表达式中的结果是常量(如“SELECT * FROM emp WHERE 2 > 1”,过滤条件“2 > 1”是常量只需要计算一次即可)的流程。由于openGauss没有提供单独的投影算子(Projection)和选择算子(Selection),Result算子也可以起到类似的作用。
Result算子提供的主要函数如表7-6所示。

表7-6 Result算子主要函数

主要函数

说明

ExecInitResult

初始化状态机

ExecResult

迭代执行算子

ExecEndResult

结束清理

ExecResultMarkPos

标记扫描位置

ExecResultRestrPos

重置扫描位置

ExecReScanResult

重置执行计划

ExecInitResult函数初始化Result状态节点,主要执行流程如下。
(1) 构造状态节点,构造ResultState状态结构。
(2) 初始化元组表。
(3) 初始化子节点表达式(生成目标列表的表达式、过滤表达式和常量表达式)。
(4) 初始化左子节点(右子节点是空)。
(5) 初始化元组类型和投影信息。
ExecResult函数迭代输出元组,流程图如图7-6所示,主要执行流程如下。
(1) 检查是否需要做常量表达式计算,如果之前没有做常量表达式计算则需要计算表达式并设置检查标识(如果常量表达式计算结果为false,则设置约束检查标识位)。
(2) 判断是否需要做投影处理,如果返回结果是集合,则把投影结果直接返回。
(3) 执行元组获取。

图7-6 Result算子执行流程

ExecEndResult函数是在执行计划执行结束时调用,用于释放执行过程申请的资源(存储空间)。

2. Append算子

Append算子对应的代码源文件是“nodeAppend.cpp”,用于处理包含一个或多个子计划的链表。Append遍历子计划链表逐个执行子计划,当子计划返回全部结果后,迭代执行下一个子计划。Append算子通常用于SQL中的集合操作中,例如多个Union All操作,可以对多个子查询的结果取并集;另外Append算子还可以用来实现继承表的查询功能。
Append算子提供的主要函数如表7-7所示。

表7-7 Append算子主要函数

主要函数

说明

ExecInitAppend

初始化Append节点

ExecAppend

迭代获取元组

ExecEndAppend

关闭Append节点

ExecReScanAppend

重新扫描Append节点

exec_append_initialize_next

为下一个扫描节点设置状态

ExecInitAppend函数初始化Append状态节点,主要执行流程如下。
(1) 初始化Append执行状态节点(AppendState)。
(2) 迭代初始化子计划链表(初始化每一个子计划)。
(3) 设置初始迭代子计划。
ExecAppend函数迭代输出元组,是Append算子主体函数。每次从子计划中获取一条元组,直到返回元组为空,则移到下一个子计划(使用as_whichplan标记),直至所有子计划都全部执行完。执行流程如图7-7所示。

图7-7 Append算子执行流程

ExecEndAppend函数负责Append节点清理,遍历子计划数组,逐一释放子计划对应的资源。

3. BitmapAnd算子

BitmapAnd算子对应的代码源文件是“nodeBitmapAnd.cpp”,用于对多个属性约束都有索引,且属性约束是And运算,对结果做And位图运算。例如:(colA约束条件)AND (colB约束条件),且colA,colB建有索引,colA对应的位图是Bitmap A,colB对应的位图是Bitmap B。位图运算如图7-8所示。

图7-8 Bitmap And操作

BitmapAnd算子提供的主要函数如表7-8所示。

表7-8 BitmapAnd算子主要函数

主要函数

说明

ExecInitBitmapAnd

BitmapAnd节点初始化

MultiExecBitmapAnd

获取Bitmap节点

ExecEndBitmapAnd

关闭BitmapAnd节点

ExecReScanBitmapAnd

重新扫描BitmapAnd节点

ExecInitBitmapAnd函数主要执行流程:首先创建Bitmapand状态节点;然后再逐一初始化子计划状态节点。

MultiExecBitmapAnd函数是BitmaAnd计划节点的主体函数,通过迭代方式做求交运算,结果集是一个新的节点。

ExecEndBitmapAnd函数是计划节点退出函数,负责关闭BitmapAnd子计划节点。

4. BitmapOr算子

BitmapOr节点同BitmapAnd节点类似,主要差异是BitmapAnd对子计划结果做求交计算(tbm_intersect),而BitmapOr是对子计划结果做并集计算(tbm_union)。BitmapOr算子提供的主要函数如表7-9所示。

表7-9 BitmapOr算子主要函数

主要函数

说明

ExecInitBitmapOr

BitmapOr节点初始化

MultiExecBitmapOr

获取Bitmap节点

ExecEndBitmapOr

关闭BitmapOr节点

ExecReScanBitmapOr

重新扫描BitmapOr节点

5. RecursiveUnion算子

RecursiveUnion算子对应的代码源文件是“nodeRecursiveUnion.cpp”,用于递归处理UNION语句。
下面给出一个例子,用SQL实现1到10递归求和,语句如下:

/* 递归求和 */
WITH RECURSIVE t_recursive_union(i)AS(
VALUES (0)
UNION ALL
SELECT i + 1 FROM t_recursive_union WHERE i < 10)
SELECT sum(i) FROM t_recursive_union;
/* 查询计划 */
Aggregate
   CTE t_recursive_union
     ->  Recursive Union
           ->  Values Scan on "*VALUES*"
           ->  WorkTable Scan on t_recursive_union
                 Filter: (i < 10)
   ->  CTE Scan on t_recursive_union

上述例子由RecursiveUnion算子处理,初始数据是VALUSE (0),然后再递归部分处理输出,即“SELECT i + 1 FROM t_recursive_union WHERE i < 10”。
RecursiveUnion使用左子树获取初始元组(初始迭代种子),使用右子树递归输出其余元组。RecursiveUnion算子提供的主要函数如表7-10所示。

表7-10 RecursiveUnion算子主要函数

主要函数

说明

ExecInitRecursiveUnion

初始化RecursiveUnion状态节点

ExecRecursiveUnion

迭代输出元组

ExecEndRecursiveUnion

清理RecursiveUnion节点

ExecReScanRecursiveUnion

重置RecursiveUnion节点

ExecReScanRecursivePlanTree

重置RecursiveUnion计划树

RecursiveUnion算子对应的关键结构体代码如下:

typedef struct RecursiveUnion

Planplan;
intwtParam;/* 对应的工作表ID */
intnumCols;/* 去重属性个数 */
AttrNumber *dupColIdx;/* 去重判断属性编号 */
Oid   *dupOperators;/* 去重判断函数 */
Oid   *dupCollations;
longnumGroups;/* 元组树估算 */
 RecursiveUnion;

ExecInitRecursiveUnion函数的主要执行流程如下。
(1) 构造递归合并状态节点,并初始化工作表(working_table)和缓存表(intermediate_table),如果需要去除重复则需要构造哈希表上下文。
(2) 初始化左子节点(用于输出初始元组作为迭代种子)和右子节点(用于迭代输出其他满足迭代条件的元组)。
(3) 创建用于去重的哈希表。
ExecRecursiveUnion函数是RecursiveUnion节点的主体函数,它的主要执行流程是:
(1) 执行左子节点,将获取元组直接返回(左子节点用于输出初始迭代种子);如需要去重则把元组加入哈希表中。
(2) 当处理完左节点(所有的初始种子已经输出),则执行右子节点获取其余元组,在执行右子节点时会逐一从工作表(working_table)获取迭代输入,并把非空的元组放入缓存表(intermediate_table)。
(3) 当工作表为空时,则把缓存表作为新的工作表,直至所有的元组都输出(缓存表和工作表都为空),如需要去重则把元组加入哈希表中。
ExecEndRecursiveUnion是清理函数,负责释放执行过程申请的存储资源(用于去重的哈希表),并关闭左子节点和右子节点。
7.3.2 扫描算子
扫描算子用于表、结果集、链表子查询等结果遍历,每次获取一条元组作为上层节点的输入。控制算子中的BitmapAnd/BitmapOr函数所需的位图与扫描算子(索引扫描算子)密切相关。主要包括顺序扫描(SeqScan)、索引扫描(IndexScan)、位图扫描(BitmapHeapScan)、位图索引扫描(BitmapIndexScan)、元组TID扫描(TIDScan)、子查询扫描(SubqueryScan)、函数扫描(FunctionScan)等。扫描算子如表7-11所示。

表7-11 扫描算子

算子名称

说明

SeqScan算子

用于扫描基础表

IndexScan算子

对表的扫描使用索引加速元组获取

BitmapIndexScan算子

通过位图索引做扫描操作

TIDScan算子

遍历元组的物理存储位置获取一个元组

SubqueryScan算子

子查询生成的子执行计划

FunctionScan算子

用于从函数返回的数据集中获取元组

ValuesScan算子

用于处理“Values (…),(…), …”类型语句,从值列表中输出元组

CteScan算子

用于处理With表达式对应的子查询

WorkTableScan算子

用于递归工作表元组输出

PartIterator算子

用于支持分区表的wise join

IndexOnlyScan算子

如索引的键值满足了查询中所有表达式的需求,可以通过只对索引扫描获得元组,避免对堆表(Heap)的访问

ForeignScan算子

扫描外部数据表

1. SeqScan算子

SeqScan算子是最基本的扫描算子,对应SeqScan执行节点,对应的代码源文件是“nodeSeqScan.cpp”,用于对基础表做顺序扫描。算子对应的主要函数如表7-12所示。

表7-12 SeqScan算子主要函数

主要函数

说明

ExecInitSeqScan

初始化SeqScan状态节点

ExecSeqScan

迭代获取元组

ExecEndSeqScan

清理SeqScan状态节点

ExecSeqMarkPos

标记扫描位置

ExecSeqRestrPos

重置扫描位置

ExecReScanSeqScan

重置SeqScan

InitScanRelation

初始化扫描表

ExecInitSeqScan函数初始化SeqScan状态节点,负责节点状态结构构造,并初始化用于存储结果的元组表。

ExecSeqScan函数是SeqScan算子执行的主体函数,用于迭代获取每一个元组。ExecSeqScan函数通过回调函数调用SeqNext函数、HbktSeqSampleNext函数、SeqSampleNext函数实现获取元组。非采样获取元组时调用SeqNext函数;如果需要采样且对应的表采用哈希桶方式存储则调用HbktSeqSampleNext函数,否则调用SeqSampleNext函数。

2. IndexScan算子

IndexScan算子是索引索引扫描算子,对应IndexScan计划节点,相关的代码源文件是“nodeIndexScan.cpp”文件。如果过滤条件涉及索引,查询计划对表的扫描使用IndexScan算子,利用索引加速元组获取。算子对应的主要函数如表7-13所示。

表7-13 IndexScan算子主要函数

主要函数

说明

ExecInitIndexScan

初始化IndexScan状态节点

ExecIndexScan

迭代获取元组

ExecEndIndexScan

清理IndexScan状态节点

ExecIndexMarkPos

标记扫描位置

ExecIndexRestrPos

重置扫描位置

ExecReScanIndexScan

重置IndexScan

ExecInitIndexScan函数负责初始化IndexScan状态节点。主要执行流程如下。

(1) 创建IndexScanState节点。
(2) 初始化子节点,初始化目标列表、索引过滤条件、原始过滤条件。
(3) 打开对应表。
(4) 打开索引。
(5) 构建索引扫描Key。
(6) 处理ORDER BY对应的Key。
(7) 启动索引扫描(返回索引扫描描述符IndexScanDesc)。
(8) 把过滤Key传递给索引器。
ExecIndexScan函数负责迭代获取元组,通过回调函数的形式调用IndexNext函数获取元组。IndexNext函数首先按照扫描Key获取元组,然后再执行表达式indexqualorig判断元组是否满足过滤条件,如果不满足则需要继续获取。
ExecEndIndexScan函数负责清理IndexScanState节点。主要执行流程如下。
(1) 清理元组占用的槽位。
(2) 关闭索引扫描描述子。
(3) 关闭索引(如果是分区表则需要关闭分区索引及分区映射)。
(4) 关闭表。

3. BitmapIndexScan算子

BitmapIndexScan算子通过位图索引做扫描操作,利用位图记录元组在存储页面的偏移位置,对应BitmapIndexScan计划节点。BitmapIndexScan算子相关的代码源文件是“nodeBitmapIndexScan.cpp”。BitmapIndexScan执行的结果是位图,该算子配合BitmapHeapScan算子获取位图对应的元组。算子对应的主要函数如表7-14所示。

表7-14 BitmapIndexScan算子主要函数

主要函数

说明

ExecInitBitmapIndexScan

初始化BitmapIndexScan状态节点

MultiExecBitmapIndexScan

获取所有元组位图

ExecEndBitmapIndexScan

清理BitmapIndexScan状态节点

ExecReScanIndexScan

重置BitmapIndexScan

ExecInitPartitionForBitmapIndexScan

初始化分区表类型

BitmapIndexScan算子对应的状态节点代码如下:

typedef struct BitmapIndexScanState 
    ScanState ss;  /* 节点状态标识 */
    TIDBitmap* biss_result;  /* 位图:扫描结果集 */
    ScanKey biss_ScanKeys; /* 索引扫描过滤表达式 */
    int biss_NumScanKeys; /* 索引扫描键数量 */
    IndexRuntimeKeyInfo* biss_RuntimeKeys; /* 索引扫描运行时求值表达式 */
    int biss_NumRuntimeKeys; /* 运行时索引扫描键数量 */
    IndexArrayKeyInfo* biss_ArrayKeys;/* 扫描键数组 */
    int biss_NumArrayKeys; /* 数组长度 */
    bool biss_RuntimeKeysReady; /* 运行时扫描键已经计算标识 */
    ExprContext* biss_RuntimeContext; /* 求值表达式上下文 */
    Relation biss_RelationDesc; /* 索引描述 */
    List* biss_IndexPartitionList; /* 分区表对应索引 */
    LOCKMODE lockMode; /* 锁模式 */
    Relation biss_CurrentIndexPartition; /* 当前对应分区索引 */
 BitmapIndexScanState;

ExecInitBitmapIndexScan函数初始化BitmapIndexScan状态节点(BitmapIndexScanState)。主要执行流程如下。
(1) 创建BitmapIndexScanState节点用于存储状态信息。
(2) 打开索引。
(3) 构建索引扫描Key。
(4) 启动索引扫描(返回索引扫描描述符IndexScanDesc)。
(5) 把过滤Key传递给索引器。
MultiExecBitmapIndexScan函数返回所有元组位图。主要执行流程如下。
(1) 准备Bitmap结果集,用于存储元组ID。
(2) 步循环批量获取元组并存储于Bitmap结果集。如果有多组过滤Key(使用函数ExecIndexAdvanceArrayKeys判断)则继续循环批量获取元组。

4. BitmapHeapScan算子

BitmapHeapSan算子通过位图(BitmapIndexScan的输出)获取实际的元组,对应的代码源文件是“BitmapHeap.cpp”。算子对应的主要函数如表7-15所示。

表7-15 BitmapHeapScan算子主要函数

主要函数

说明

ExecInitBitmapHeapScan

初始化BitmapHeapScan状态节点

ExecBitmapHeapScan

迭代获取元组

ExecEndBitmapHeapScan

清理BitmapHeapScan状态节点

ExecReScanBitmapHeapScan

重置BitmapHeapScan

BitmapHeapScan算子对应的状态节点代码如下:

typedef struct BitmapHeapScanState 
    ScanState ss; /* 节点标识 */
    List* bitmapqualorig; /* 元组过滤条件 */
    TIDBitmap* tbm; /* 位图:来自BitmapIndexScan节点输出 */
    TBMIterator* tbmiterator; /* 位图迭代器 */
    TBMIterateResult* tbmres; /* 迭代结果 */
    TBMIterator* prefetch_iterator; /* 预抓取迭代器 */
    int prefetch_pages; /* 预获取页面数量 */
    int prefetch_target; /* 当前获取页面 */
 BitmapHeapScanState;

ExecInitBitmapHeapScan函数负责初始化BitmapHeapScan状态节点(BitmapHeapScanState)。主要执行流程如下。
(1) 创建BitmapHeapScanState状态节点。
(2) 初始化子节点,初始化目标列表、索引过滤条件、原始过滤条件。
(3) 打开对应表。
(4) 初始化元组槽位并设置元组迭代获取函数。
(5) 启动表扫描(返回表扫描描述符TableScanDesc)。
(6) 初始化左子节点(左子节点负责执行位图索引扫描,并返回位图)。
ExecBitmapHeapScan函数负责迭代输出元组。使用回调函数获取元组,依照表的类型调用BitmapHeapTblNext函数或BitmapHbucketTblNext(哈希桶类型)函数。BitmapHeapTblNext函数的主要执行流程是:首先初始化位图,然后使用位图迭代器tbmres获取元组偏移位置,最后从缓冲区获取元组slot。
ExecEndBitmapHeapScan函数负责清理BitmapHeapScan状态节点,清理流程类似于ExecEndIndexScan函数。

5. TIDScan算子

TIDScan算子用于通过遍历元组的物理存储位置获取每一个元组(TID由块编号和偏移位置组成),对应TIDScanState计划节点,相应的代码源文件是“nodeTIDScan.cpp”。算子对应的主要函数如表7-16所示。

表7-16 TIDScan算子主要函数

主要函数

说明

ExecInitTidScan

初始化TIDScan状态节点

ExecTidScan

迭代获取元组

ExecEndTidScan

清理TIDScan状态节点

ExecReScanTidScan

重置TIDScan

TID扫描算子对应的状态节点代码如下:

typedef struct TidScanState 
    ScanState ss;       /* 节点标识 */
    List* tss_tidquals; /* tid过滤表达式 */
    bool tss_isCurrentOf; /* 游标同当前扫描表是否匹配 */
    Relation tss_CurrentOf_CurrentPartition; /* 当前扫描分区 */
    int tss_NumTids; /* tid数量 */
    int tss_TidPtr; /* 当前扫描位置 */
    int tss_MarkTidPtr; /* 标记扫描位置 */
    ItemPointerData* tss_TidList; /* tid列表 */
    HeapTupleData tss_htup; /* 堆元组 */
    HeapTupleHeaderData tss_ctbuf_hdr; /* 堆元组头信息 */
 TidScanState;

ExecInitTidScan是TIDScan节点状态初始化函数。主要执行流程如下。
(1) 创建TidScanState节点。
(2) 初始化子节点,初始化目标列表、索引过滤条件、原始过滤条件。
(3) 打开对应表。
(4) 初始化结果元组;
(5) 启动表扫描(返回表扫描描述符TableScanDesc)。
ExecTidScan是元组迭代获取函数,通过调用TidNext函数实现功能。TidNext函数首先获取Tid列表,并存放到tss_TidList数组中,根据heap_relation调用TidFetchTuple函数或HbtTidFetchTuble函数(哈希桶类型)中逐一获取元组(tss_TidPtr是tid在数组中的相对偏移位置,使用函数InitTidPtr移动偏移位置)。

6. SubqueryScan算子

SubqueryScan算子以子计划为扫描对象,实际执行会

opengauss数据库源码解析系列文章——sql引擎源解析(代码片段)

本篇我们开启“SQL引擎源解析”中“6.1概述”及“6.2SQL解析”的精彩内容介绍。第6章SQL引擎源解析SQL引擎作为数据库系统的入口,主要承担了对SQL语言进行解析、优化、生成执行计划的作用。对于用户输入的SQL语句,SQL... 查看详情

opengauss数据库源码解析系列文章——ai技术(代码片段)

上一篇介绍了第七章执行器解析中“7.6向量化引擎”及“7.7小结”的相关内容,本篇我们开启第八章AI技术中“8.1概述”及“8.2自调优”的相关精彩内容介绍。AI技术最早可以追溯到20世纪50年代,甚至比数据库系统的发展... 查看详情

opengauss数据库源码解析系列文章——存储引擎源码解析(代码片段)

...aceUpdate更新模式,中文意思为:原地更新,是openGauss内核新增的一种存储模式。openGauss内核 查看详情

opengauss数据库源码解析系列文章——存储引擎源码解析(代码片段)

...储引擎,针对众核和大内存服务器进行了优化。MOT是openGauss数据库的一个先进特性& 查看详情

opengauss数据库源码解析系列文章——存储引擎源码解析(代码片段)

...绍。4.2.5行存储索引机制本节以B-Tree索引为例,介绍openGauss中行存储(格式)表的索引机制。索引本质上是对数据的一种物理有序聚簇。有序聚簇参考的排序字段被称 查看详情

opengauss数据库源码解析系列文章——存储引擎源码解析(代码片段)

上一篇我们讲述了“4.2磁盘引擎”中“4.2.1磁盘引擎整体框架及代码概览”与“4.2.2行存储统一访存接口”。本篇我们将讲述“4.2.3astore”。4.2.3astoreastore整体框架astore整体框架如图4-2所示。如上所述,作为行存储子格式之一&#... 查看详情

⭐opengauss数据库源码解析系列文章——对象权限管理⭐(代码片段)

...。9.4对象权限管理权限管理是安全管理重要的一环,openGauss权限管理基于访问控制列表(accesscontrollist,ACL&#x 查看详情

opengauss数据库源码解析系列文章——事务机制源码解析(代码片段)

上一篇为介绍完"5.1事务整体架构和代码概览"及“5.2事务并发控制”,本篇将继续介绍“5.3锁机制”的精彩内容。5.3锁机制数据库对公共资源的并发控制是通过锁来实现的,根据锁的用途不同,通常可以分为3种&... 查看详情

opengauss数据库源码解析系列文章——sql引擎源解析(代码片段)

...查询优化”及“6.4小结”的相关内容的介绍。6.3查询优化openGauss数据库的查询优化过程功能比较明晰,从源代码组织的角度来看,相关代码分布在不同的目录下,如表6-6所示。表6-6查询优化模块说明模块目录说明查询... 查看详情

opengauss数据库源码解析系列文章——审计与追踪(代码片段)

...常有两种:记录到数据库的表中、记录到OS文件中。openGauss采用记录到OS文件中(即审计日志& 查看详情

⭐opengauss数据库源码解析系列文章——ai查询时间预测⭐(代码片段)

上一篇介绍了“8.5指标采集、预测与异常检测”的相关内容,本篇我们介绍“8.6AI查询时间预测”的相关精彩内容介绍。8.6AI查询时间预测在前面介绍过“慢SQL发现”特性,该特性的典型场景是新业务上线前的检查,... 查看详情

⭐opengauss数据库源码解析系列文章——角色管理⭐(代码片段)

❤️‍大家好,我是Gauss松鼠会,欢迎进来学习啦~❤️‍在前面介绍过“9.1安全管理整体架构和代码概览、9.2安全认证”,本篇我们介绍第9章安全管理源码解析中“9.3角色管理”的相关精彩内容介绍。9.3角色管理角... 查看详情

⭐opengauss数据库源码解析系列文章——deepsql⭐(代码片段)

...另外一个大方向,即DB4AI。在本章中,我们将介绍openGauss的 查看详情

opengauss数据库源码解析系列文章——ai技术之“指标采集预测与异常检测”(代码片段)

上一篇介绍了“8.4智能索引推荐”的相关内容,本篇我们介绍“8.5指标采集、预测与异常检测”的相关精彩内容介绍。8.5指标采集、预测与异常检测数据库指标监控与异常检测技术,通过监控数据库指标,并基于时序... 查看详情

opengauss数据库源码解析系列文章——数据安全技术(上)(代码片段)

...理源码解析中“9.6数据安全技术”的相关精彩内容介绍。openGauss采用了多种加密解密技术来提升数据在各个环节的安全性。9.6.1数据加解密接口用户在使用数据库时,除了需要基本的数据库安全之外,还会对导入的数据进... 查看详情

tensorflow源码解析系列文章索引

文章索引framework解析resourceallocatortensoropnodekernelgraphdevicefunctionshape_inferencecommon_runtime解析devicesessiongraph_optimizerexecutor-1executor-2direct_session后记关于起源阅读tensorflow源码时,为了敦促自己主动思考,把阅读的笔 查看详情

数据库迁移系列从oracle迁移到opengauss实战分享(代码片段)

之前的迁移系列中我们介绍了Mysql到openGauss的迁移方法,本篇介绍使用Ora2og工具从Oracle到openGauss数据库的迁移。文章目录简介迁移前准备环境软件安装ora2og工具安装创建迁移项目配置ora2pg.conf测试迁移导出导入Ora2Pg不足FAQ简介... 查看详情

数据库迁移系列从mysql到opengauss的数据库对象迁移实践(代码片段)

在之前这一篇中我们分享过使用chameleon工具完成MySQL到openGauss的全量数据复制、实时在线复制。9.30新发布的openGauss3.1.0版本,工具的全量迁移和增量迁移的性能不但有了全面提升,而且支持数据库对象视图、触发器、自定... 查看详情