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 查询。
2.PORTAL_ONE_RETURNING
包含一个 INSERT/UPDATE/DELETE 查询,且带 RETURNING 条件。
3.PORTAL_ONE_MOD_WITH
包含一个 SELECT 查询并且有修改的 CTE。
需要注意,例如下面这个查询:
这个并不是 PORTAL_ONE_MOD_WITH 查询,而是 PORTAL_MULTI_QUERY。
4.PORTAL_UTIL_SELECT
包含一个 utility 语句,且该语句执行会返回像 SELECT 那样有输出结果。
5.其他情况
例如:
PORTAL_MULTI_QUERY + PORTAL_MULTI_QUERY
•状态
○PORTAL_NEW
○PORTAL_DEFINED
○PORTAL_READY
○PORTAL_QUEUE
○PORTAL_ACTIVE
○PORTAL_DONE
○PORTAL_FAILED
这几个状态会在下面依次引入。
1.3 CreatePortal
创建一个新的 Portal,传入参数均为: CreatePortal("", true, true),表示创建一个匿名的 Portal,允许重复且重复后保持沉默。
CreatePortal 逻辑:
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。
•不同 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), 然后每次读取几行命令结果。
例如:
该命令运行机制为:
首先识别到是一个数据定义语句,便会调用 ProcessUtility,随后解析从 PlannedStmt 中的 utilityStmt 识别出是一个 T_DeclareCursorStmt 节点,调用 PerformCursorOpen 执行 Declare cursor 命令。
PerformCursorOpen 处理逻辑如下:
•query 重写
•优化器优化,生成 PlannedStmt
•创建 Portal(名字为游标名),仅调用 PortalStart
2.2 关闭游标
关闭游标,实际就是关闭 Portal,调用 PerformPortalClose。
如下两个操作:
如果传入的名字为空,则是 CLOSE ALL 关闭所有非活跃 Portal,否则,只关闭指定的 Portal(Cursor)。
2.3 FETCH or MOVE
FETCH 与 MOVE 语法分别如下:
FETCH 从游标中检索 n 行到目标中, 目标可以是一个行变量、记录变量、逗号分隔的普通变量列表, 就像 SELECT INTO 一样, 如果没有获取到数据,目标会设为 NULL。
MOVE 重新定位一个游标,而不需要检索任何数据,例如:一旦游标位置确定,则可以删除或更新行。
从实现层面两者都会进入到 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。
Portal 内部封装了三个宏,分别是:
PortalHashTableLookup
PortalHashTableInsert
PortalHashTableDelete
下图左侧是这三个宏的功能,右边是对外核心接口。
结语:
Portal 作为执行引擎最基础的部分,其核心功能包括策略选择、启动执行器、设置游标。根据不同的 SQL 语句类型,Portal 会选择不同的执行路径完成数据的存取工作。
版权声明: 本文为 InfoQ 作者【HashData】的原创文章。
原文链接:【http://xie.infoq.cn/article/7e87399b921bacb57957d62d8】。文章转载请联系作者。
评论