背景
He3DB for PostgreSQL是受Aurora论文启发,基于开源数据库PostgreSQL 改造的数据库产品。架构上实现计算存储分离,并进一步支持数据的冷热分层,大幅提升产品的性价比。
He3DB for PostgreSQL中查询规划用于选择SQL语句执行代价最小的方案。它在整个查询处理模块应该是在一个非常重要的地位上,这一步直接决定了查询的方式与路径,很大程度上影响了数据库查询的查询性能。
1、概述
下图大概的刻画了查询规划模块里主要的函数调用关系
查询规划的最终目的是得到可被执行器执行的最优计划,整个过程可分为预处理
、生成路径
和生成计划
三个阶段。
预处理
阶段是对查询树(Query结构体)的进一步改造,这种改造可通过SQL语句体现。在此过程中,最重要的是提升子链接和提升子查询。生成路径
阶段,接收到改造后的查询树后,采用动态规划算法或遗传算法,生成最优连接路径和候选的路径链表。生成计划
阶段,用得到的最优路径,首先生成基本计划树(查询语的SELECT…FROM…WHERE部分,),然后添加GROUPBY、HAVING和ORDER BY等子句所对应的计划节点形成完整计划树。
本文着重介绍预处理
阶段。预处理部分主要是对查询树Query中的范围表rtable 和连接树 jointree 等进行处理。主要分为三个阶段:提升子链接和子查询
、预处理表达式
、处理HAVING子句
。
2、提升子链接/子查询
SQL语句中一个“SELECT…FROM…WHERE”语句称为一个查询块,将一个查询块嵌套到另一个查询块的FROM子句、WHERE子句或HAVING子句中的查询称为嵌套查询
,其中被嵌入其他查询块中的查询块称为嵌套子查询。在PostgreSQL中子链接用来表示出现在表达式中的子查询与普通子查询的联系和区别。
子查询
:一条完整的查询语句。
子链接
:子链接是一条表达式,但是表达式内部也可以包含查询语句。
直观上来说就是:子查询是放在FROM子句里的而子链接则出现在WHERE子句或者HAVING子句中。
按关键字,嵌套查询可以分为以下几类:
EXISTS
:声明了EXISTS 的子查询。ALL
:声明了 ALL或 NOT IN的子查询。ANY
:声明ANY或IN的子查询。EXPR
:子查询返回一个参数给外层父查询。MULTIEXPR
:子查询返回多个参数给外层父查询,例如语句“SELECTFROMBWHERE(b1,3,'aa’)>SELECTfrom A;"中的子査询将向父查询返回多个属性值;ARRAY
:子查询是将某些值构成数组的表达式,例如:“SELECT ARRAY[1,2,3+4];”
举一个例子展示对子链接/子查询和子查询的处理,假设我们有这样一个SQL语句:
· SELECT D.dname
· FROM dept D
· WHERE D.deptno IN
· (SELECT E.deptno FROM emp E WHERE E.sal = 100);
从字面上看,如果该语句中的子查询被独立地规划,也就是说对于表dept中的每一个元组deptno值,都要搜索一遍emp表。显然这样的做法代价也非常大。但是如果我们把子查询提升并合并到父查询中,那么我们看看效果。
先做提升子链接
:
· SELECT D.dname
· FROM dept D , (SELECT E.deptno FROM emp E WHERE E.sal = 100) AS Sub
· WHERE D.deptno = Sub.deptno;
然后再做提升子查询
:
· SELECT D.dname
· FROM dept D ,emp E
· WHERE D.deptno = E.deptno and E.sal = 100;
可以看到,这样操作以后的SQL语句只要先做一下过滤(E.sal = 100),然后再把结果和dept表做一下连接即可,大大提高了查询效率。
2.1 子链接提升流程
void pull_up_sublinks(PlannerInfo *root)
{
Node *jtnode; /* 用于存储处理后的联合树(jointree)节点 */
Relids relids; /* 用于存储相关关系标识符的集合 */
/* 如果查询条件中包含行号(rownum),则禁止提升子链接 */
if (find_rownum_in_quals(root))
{
return; /* 如果找到行号,直接返回,不进行后续处理 */
}
/* 开始递归遍历联合树(jointree) */
jtnode = pull_up_sublinks_jointree_recurse(root,
(Node *) root->parse->jointree,
&relids);
/*
* root->parse->jointree 必须始终是一个 FromExpr,因此如果递归返回了一个裸的 RangeTblRef
* 或 JoinExpr,我们需要插入一个虚拟的 FromExpr。
*/
if (IsA(jtnode, FromExpr))
root->parse->jointree = (FromExpr *) jtnode; /* 如果 jtnode 是 FromExpr,直接赋值 */
else
root->parse->jointree = makeFromExpr(list_make1(jtnode),
在subquery_planner
函数里,调用pull_up_sublinks
函数处理WHERE子句和JOIN/ON子句中的ANY
和EXISTS
类型的子链接。
在用pull_up_sublinks
函数内部,调用pull_up_sublinks_jointree_recurse
函数递归地处理连接树jointree
:
- 对于RangeTblRef类型,直接返回;
- 对于FromExpr类型,递归调用pull_up_sublinks_jointree_recurse函数处理每个节点并调用pull_up_sublinks_qual_recurse函数处理约束条件;
- 对于JoinExpr类型,递归调用pull_up_sublinks_jointree_recurse函数处理左右子树并调用pull_up_sublinks_qual_recurse函数处理约束条件.
2.2 子查询提升
void
pull_up_subqueries(PlannerInfo *root)<