写点什么

PostgreSQL 技术内幕 (四) 执行引擎之 Portal

作者:HashData
  • 2022-12-31
    北京
  • 本文字数:4276 字

    阅读完需:约 14 分钟

PostgreSQL 技术内幕(四)执行引擎之Portal

序言:

在 Postgre 数据库中,执行引擎作为一个连接查询引擎与存储引擎的中间部件,其主要的作用就是根据查询引擎传递的计划进行执行,获取数据。

Portal(门户),相当于执行引擎的入口。当一条 SQL 在经过查询引擎处理的词法、语法、查询优化、访问路径的创建后,都要经过 Portal 来决定执行器的路径,并将结果返回给用户,来完成数据的存取工作。

导语:

Portal,也称为策略选择模块,这也代表了 Portal 最核心的功能。通常,Portal 会根据 SQL 语句的类型,选择不同的执行模块(ProcessUtility、Executor 等)。

SQL 语句类型包括:可优化语句、数据定义语句。

  • 可优化语句

包括 DML(insert/update/select)等语句,这类语句特点是查询满足条件的元组,返回给用户或者元组操作后写入磁盘。之所以称其为可优化语句,是因为这类语句通常会被优化器进行重写与优化,从而加快查询速度。

  • 数据定义语句

主要是功能性语句,例如:DDL(Create、Alter、Drop)、DCL(Grant、COMMIT、ROLLBACK)等。

下图为 Portal 在整个 SQL 执行过程中所承担的职责。图中上面 SQL 为可优化语句,下面为数据定义语句。

1.Portal 内核揭秘:

简单来说,Portal 负责驱动执行器,是执行器中的一部分。

1.1 入口层

如上图所示,Portal 通常分为两类:

QD 执行会从 exec_simple_query 进入;

QE 执行从 exec_mpp_query 进入;

然后统一由 portal 来根据语句类型进行决策,调用不同的执行器结构。

•ProcessUtilitySlow(事件触发类语句)

•ExecutorStart

 初始化 Estate、QueryDesc,调用 InitPlan

•ExecutorRun

 执行各个节点获取数据

•ExecutorFinish

 执行 ModifyTable 节点

1.2 Portal 层

首先初识 Portal 内部数据结构:


通过上面的整体结构图可以看出,Portal 在执行引擎中起到了承上启下的作用:向上,它存储了优化器的相关信息;向下,它存储了执行引擎相关的一些结构。同时,Portal 自身也存储了一些比较基础的结构,以及对应游标的结构。

作为 Portal 的核心功能,Portal 策略通常有五类:

1.PORTAL_ONE_SELECT

只包含一个 SELECT 查询。

SQLselect * from t1;
复制代码

2.PORTAL_ONE_RETURNING

包含一个 INSERT/UPDATE/DELETE 查询,且带 RETURNING 条件。

SQLINSERT INTO ret_tbl (id) VALUES (3) RETURNING id INTO tableId;
复制代码

3.PORTAL_ONE_MOD_WITH

包含一个 SELECT 查询并且有修改的 CTE。

SQLWITH ins AS (  INSERT INTO t1 (t1_id) VALUES(1) RETURNING t1_id) SELECT * from ins;
复制代码

需要注意,例如下面这个查询:

SQLWITH ins AS (  SELECT * from t1) INSERT INTO t2  (t2_id, col2)SELECT * from ins;
复制代码

这个并不是 PORTAL_ONE_MOD_WITH 查询,而是 PORTAL_MULTI_QUERY。

4.PORTAL_UTIL_SELECT

包含一个 utility 语句,且该语句执行会返回像 SELECT 那样有输出结果。

SQLpostgres=# explain select * from t1;                                  QUERY PLAN                                   -------------------------------------------------------------------------------Gather Motion 3:1  (slice1; segments: 3)  (cost=0.00..431.00 rows=1 width=12)   ->  Seq Scan on t1  (cost=0.00..431.00 rows=1 width=12)Optimizer: Pivotal Optimizer (GPORCA)(3 rows)
postgres=# EXECUTE t1_fn_ret(2, 'helloworld');t1_id | col1 -------+------------ 2 | helloworld(1 row)
INSERT 0 1
复制代码

5.其他情况

例如:

PORTAL_MULTI_QUERY + PORTAL_MULTI_QUERY

SQLPREPARE t1_fn (int, text) AS INSERT INTO t1 VALUES($1, $2);
复制代码

状态

○PORTAL_NEW

○PORTAL_DEFINED

○PORTAL_READY

○PORTAL_QUEUE

○PORTAL_ACTIVE

○PORTAL_DONE

○PORTAL_FAILED

 这几个状态会在下面依次引入。

1.3 CreatePortal

创建一个新的 Portal,传入参数均为: CreatePortal("", true, true),表示创建一个匿名的 Portal,允许重复且重复后保持沉默。

CreatePortal 逻辑:

Plain TextPortalCreatePortal(const char *name, bool allowDup, bool dupSilent)
复制代码


1.根据传入的第一个参数 name 从哈希表中查找。

2.根据传入的第二个参数 allowDup,如果第一步查找到,从哈希表中决定是否删除。如果 true,则删除,否则报错。在哈希表中查找到 Portal 且允许重复的情况下,在 QD 节点上会根据第三个参数 dupSilent 决定是否输出告警信息。

3.创建一个新的 Portal,并初始化相应参数。

执行完毕后,便创建好了一个状态为 PORTAL_NEW 的 Portal。

1.4 PortalDefineQuery

定义 Portal 数据,包含了:查询语句 sourceText、PlannedStmts、查询完成标记 qc。

注意:QD 上根据传递进来的 stmt 来设置 nodeTag,但是 QE 上为 T_Query,因为 QE 上不是 parsed statement,所以不是 T_SelectStmt。

最终设置 Portal 状态为 PORTAL_DEFINED。

1.5 PortalStart

准备好 portal,主要有如下几步:

1.设置 ddesc,该信息为 QD 到 QE 上的额外信息,QD 上为 NULL,QE 上不为 NULL。

2.设置全局参数,例如:当前活跃的 Portal、resourceOwner、context。

3.设置 Portal 参数字段:PortalParams,同样 QD 上为 NULL,QE 上不为 NULL。

4.设置 Portal 策略(ChoosePortalStrategy)。

如下图所示:输入为查询计划链表,针对以下情况:

  • PORTAL_ONE_SELECT

  • PORTAL_ONE_MOD_WITH

  • PORTAL_UTIL_SELECT

  • PORTAL_ONE_RETURNING

首先判断是 Query 还是 PlannedStmt,一般情况下的查询语句基本都是 PlannedStmt。

对于像 PREPARE st(int) as select * from t1 之类 utility 语句,第一次调用 ChoosePortalStrategy,返回 PORTAL_MULTI_QUERY(命中 PlannedStmt),第二次调用返回 PORTAL_ONE_SELECT(命中 Query)。

根据 Portal 策略初始化 Portal,最重要的是初始化 tupDesc 与 Cursor postion。

例如:"QUERY PLAN"、"t1_id、col1"就是 tupDesc。

SQLpostgres=# explain select * from t1;                                  QUERY PLAN                                   -------------------------------------------------------------------------------Gather Motion 3:1  (slice1; segments: 3)  (cost=0.00..431.00 rows=1 width=12)   ->  Seq Scan on t1  (cost=0.00..431.00 rows=1 width=12)Optimizer: Pivotal Optimizer (GPORCA)(3 rows)
postgres=# EXECUTE t1_fn_ret(2, 'helloworld');t1_id | col1 -------+------------ 2 | helloworld(1 row)
INSERT 0 1
复制代码

•不同 tupleDesc 函数区别

○ExecTypeFromTL

▪with resjunk column

○ExecCleanTypeFromTL

▪skip resjunk column

1.在执行 Portal 过程中发生异常,设置 Portal 的状态为 PORTAL_FAILED;否则,下一步。

2.设置 Portal 状态为 PORTAL_READY。

1.6 PortalRun

根据 SQL 的语句类型选择不同的执行路径,获取元组数据,完成 Portal 工作,运行完之后执行完毕,或者进行下一轮(READY,而非 ACTIVE)。

Portal 策略执行路径如下:

•PORTAL_ONE_SELECT

○set result = Portal->atEnd

•PORTAL_ONE_RETURNING|PORTAL_ONE_MOD_WITH|PORTAL_UTIL_SELECT

○填充 holdStore(见下方)

○调用 PortalRunSelect 返回 n 行数据

▪获取时数据方向包含前进/后退

▪可以从 holdStore 中获取,也可以从 ExectorRun 中获取

○设置状态为 PORTAL_READY

○设置是否完成运行标记为 Portal->atEnd

•PORTAL_MULTI_QUERY

○调用 PortalRunMulti

○设置状态为 PORTAL_Done

○设置是否完成运行标记为 true

此外,上述图中填充 holdStore 逻辑如下:

•调用 PortalCreateHoldStore,填充 portal->holdStore,然后通过工厂函数 CreateDestReceiver 构造 DestReceiver(子类:TStoreState);

•根据 Portal 策略执行查询:

PORTAL_ONE_RETURNING

PORTAL_ONE_MOD_WITH

这两个策略调用 PortalRunMulti

PORTAL_UTIL_SELECT 调用 PortalRunUtility。

○PORTAL_ONE_RETURNING

○PORTAL_ONE_MOD_WITH

 ▪PortalRunMulti

  •utilityStmt

   ○PortalRunUtility

  •not utilityStmt

   ○ProcessQuery

○PORTAL_UTIL_SELECT

 ▪PortalRunUtility

2.游标 Cursor

2.1 打开游标

如果不想一次执行整个命令,可以设置一个封装该命令的游标(Cursor), 然后每次读取几行命令结果。

SQLname [ [ NO ] SCROLL ] CURSOR [ ( arguments ) ] FOR query;
复制代码

例如:

SQLDECLARE liahona SCROLL CURSOR FOR SELECT * FROM t1;
复制代码

该命令运行机制为:

首先识别到是一个数据定义语句,便会调用 ProcessUtility,随后解析从 PlannedStmt 中的 utilityStmt 识别出是一个 T_DeclareCursorStmt 节点,调用 PerformCursorOpen 执行 Declare cursor 命令。

PerformCursorOpen 处理逻辑如下:

•query 重写

•优化器优化,生成 PlannedStmt

•创建 Portal(名字为游标名),仅调用 PortalStart

2.2 关闭游标

关闭游标,实际就是关闭 Portal,调用 PerformPortalClose。

如下两个操作:

SQLCLOSE cursor_name;CLOSE ALL;
复制代码

如果传入的名字为空,则是 CLOSE ALL 关闭所有非活跃 Portal,否则,只关闭指定的 Portal(Cursor)。

2.3 FETCH or MOVE

FETCH 与 MOVE 语法分别如下:

SQLFETCH [ direction { FROM | IN } ] cursor INTO target;MOVE [ direction { FROM | IN } ] cursor;
复制代码

FETCH 从游标中检索 n 行到目标中, 目标可以是一个行变量、记录变量、逗号分隔的普通变量列表, 就像 SELECT INTO 一样, 如果没有获取到数据,目标会设为 NULL。

MOVE 重新定位一个游标,而不需要检索任何数据,例如:一旦游标位置确定,则可以删除或更新行。

SQLMOVE cursor_variable;UPDATE table_name SET column = value, ... WHERE CURRENT OF cursor_variable;
复制代码

从实现层面两者都会进入到 PerformPortalFetch,都被解析为 FetchStmt,内部有个成员 ismove 决定是 MOVE 还是 FETCH。

不管是哪个,都会指定 Cursor 的名字,有了这个名字,便知道了 Portal,随后调用 PortalRunFetch 来获取结果。

PortalRunFetch 内部会像 PortalRun 运行一样,首先设置 Portal 状态为 Active,随后根据策略选择不同的调用链。

•PORTAL_ONE_SELECT

○调用 DoPortalRunFetch

•PORTAL_ONE_RETURNING|PORTAL_ONE_MOD_WITH|PORTAL_UTIL_SELECT

○首先判断 portal 内部是否有 holdStore,如果没有会调用 FillPortalStore,随后调用 DoPortalRunFetch。

DoPortalRunFetch 内部实现,会考虑传入的 direction,决定是前向还是后向等不同方向的扫描,最后调用 PortalRunSelect 获取数据。注意:gpdb 不支持 backward scan,但是 pg 支持。

3.Portal 内存管理

限制每个用户最大打开的 Portal 数量为 16。

Plain Text#define PORTALS_PER_USER    16
复制代码

Portal 内部封装了三个宏,分别是:

  • PortalHashTableLookup

  • PortalHashTableInsert

  • PortalHashTableDelete

下图左侧是这三个宏的功能,右边是对外核心接口。

结语:

Portal 作为执行引擎最基础的部分,其核心功能包括策略选择、启动执行器、设置游标。根据不同的 SQL 语句类型,Portal 会选择不同的执行路径完成数据的存取工作。

发布于: 刚刚阅读数: 3
用户头像

HashData

关注

还未添加个人签名 2021-03-10 加入

云原生企业级数据仓库

评论

发布
暂无评论
PostgreSQL 技术内幕(四)执行引擎之Portal_postgresql_HashData_InfoQ写作社区