暂无图片
暂无图片
2
暂无图片
暂无图片
暂无图片

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

HashData 2023-01-03
2034
序言:
在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查询。
SQL
select * from t1;
2.PORTAL_ONE_RETURNING
包含一个INSERT/UPDATE/DELETE查询,且带RETURNING条件。
SQL
INSERT INTO ret_tbl (id) VALUES (3) RETURNING id INTO tableId;
3.PORTAL_ONE_MOD_WITH
包含一个SELECT查询并且有修改的CTE。
SQL
WITH ins AS (
  INSERT INTO t1 (t1_id) VALUES(1) RETURNING t1_id

SELECT * from ins;
需要注意,例如下面这个查询:
SQL
WITH 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那样有输出结果。
SQL
postgres=# 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
SQL
PREPARE 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 Text
Portal
CreatePortal(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。
SQL
postgres=# 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), 然后每次读取几行命令结果。
SQL
name [ [ NO ] SCROLL ] CURSOR [ ( arguments ) ] FOR query;
例如:
SQL
DECLARE liahona SCROLL CURSOR FOR SELECT * FROM t1;
该命令运行机制为:
首先识别到是一个数据定义语句,便会调用ProcessUtility,随后解析从PlannedStmt中的utilityStmt识别出是一个T_DeclareCursorStmt节点,调用PerformCursorOpen执行Declare cursor命令。
PerformCursorOpen处理逻辑如下:
query重写
优化器优化,生成PlannedStmt
创建Portal(名字为游标名),仅调用PortalStart
2.2 关闭游标
关闭游标,实际就是关闭Portal,调用PerformPortalClose。
如下两个操作:
SQL
CLOSE cursor_name;
CLOSE ALL;
如果传入的名字为空,则是CLOSE ALL关闭所有非活跃Portal,否则,只关闭指定的Portal(Cursor)。
2.3 FETCH or MOVE
FETCH与MOVE语法分别如下:
SQL
FETCH [ direction { FROM | IN } ] cursor INTO target;
MOVE [ direction { FROM | IN } ] cursor;
FETCH从游标中检索n行到目标中, 目标可以是一个行变量、记录变量、逗号分隔的普通变量列表, 就像SELECT INTO一样, 如果没有获取到数据,目标会设为NULL。
MOVE重新定位一个游标,而不需要检索任何数据,例如:一旦游标位置确定,则可以删除或更新行。
SQL
MOVE 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会选择不同的执行路径完成数据的存取工作。

点击“阅读原文”可观看直播视频回放,与我们共同成长~
相关阅读:
PostgreSQL技术内幕(一):数据组织
PostgreSQL 技术内幕(二) Greenplum-AO表
PostgreSQL 技术内幕(三)聚集算子

END


HashData研发、行业销售、工程服务等岗位正在火热招聘中,欢迎扫描下方图中的二维码,获取职位详细信息,加入我们的团队!

最后修改时间:2023-01-03 10:20:48
文章转载自HashData,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论