openGauss学习——优化 UNION ALL 语句
在本篇博客中,我对查询规划的预处理过程中对UNION ALL语句的优化过程进行了简单介绍。
引言
本篇博客中介绍SQL查询规划的预处理过程中,对 UNION ALL 语句的优化过程。
文件路径
/src/gausskernel/optimizer/prep/ prepnonjointree.cpp
优化 UNION ALL
在查询规划的预处理过程中,subquery_planner() 会对顶层的 UNION ALL 语句进行处理,目的是将 UNION ALL 这种集合操作的形式转换为AppendRelInfo的形式,实现函数为flatten_simple_union_all() 。
说地具体些,如果一个查询包含的 setop(setOperations) tree 完全由简单的 UNION ALL操作组成,那么函数会将集合树 setop tree 压平,变成一个扁平的追加关系,这样就可以把集合结构简化,不需要在树形结构中处理集合了。
例如查询语句:
SELECT * from ( SELECT * from A UNION ALL ( SELECT * from B UNION ALL SELECT * from ( SELECT * from C UNION ALL SELECT * from D )) ) ;
将其 jointree(setop tree)压平如下图所示:
另外需要注意的是,函数 flatten_simple_union_all() 只针对查询顶层的 setop tree 结点进行压平操作,因为对于FROM语句中的子查询,subquery_planner() 在调用此函数之前已经调用函数 pull_up_subquery() ,并通过 pull_up_simple_UNION_all() 函数完成了UNION语句的拉平。因此函数 flatten_simple_union_all() 不针对子查询中的 UNION 结构。
UNION 操作符
在这里简单介绍下SQL查询语句中的 UNION 操作符。UNION 用于合并两个或多个 SELECT 语句的结果集,需要注意的是,UNION 内部的 SELECT 语句必须拥有相同数量的列,列也必须拥有相似的数据类型。同时,每条 SELECT 语句中的列的顺序必须相同。
SQL UNION 语法
SELECT column_name(s) FROM table_name1
UNION
SELECT column_name(s) FROM table_name2
SQL UNION ALL 语法
SELECT column_name(s) FROM table_name1
UNION ALL
SELECT column_name(s) FROM table_name2
需要注意的是,默认情况下,UNION操作符会选取两个子集中具有不相同字段的表项,相当于简单的并集操作;因此如果允许结果中出现完全重复的值,应使用 UNION ALL语句。
另外,UNION 结果集中的列名总是等于 UNION 中第一个 SELECT 语句中的列名。下面举例说明 UNION 和 UNION ALL 之间的差别。例如如下两个表A,B,将他们做 UNION 和 UNION ALL 操作;
得到的结果为:
函数 flatten_simple_union_all()
void flatten_simple_union_all(PlannerInfo* root)
接下来看函数实现。
首先,如果 setop tree 的顶层结点的结构类型不为SetOperationStmt或 setop tree 包含递归的UNION结构,不对其做优化。关于结构体 SetOperationStmt 我们在 SQL optimizer解读(4)—— 替换空FROM语句 已经介绍过,它是集合树的顶层结点,它包含了查询中使用的集合操作类型以及对应的子查询。
/* Shouldn't be called unless query has setops */
topop = (SetOperationStmt*)parse->setOperations;
AssertEreport(topop != NULL && IsA(topop, SetOperationStmt),
MOD_OPT_REWRITE,
"SetOperationStmt shouldn't be NULL in flatten_simple_union_all");
/* Can't optimize away a recursive UNION */
if (root->hasRecursion)
return;
接下来遍历集合树,确定其是否只包含简单的UNION ALL语句,如果不是,不对其做压平操作。
if (!is_simple_union_all_recurse((Node*)topop, parse, topop->colTypes) || ng_is_multiple_nodegroup_scenario())
return;
接下来找到 setop tree 最左侧的叶子结点(leftmostjtnode的类型声明为Node *,但其实际上指向一个RangeTblRef类型的RTE结点),这个结点对应的RTE将在压平之后充当“根结点”的作用,其他叶子结点引用的RTE信息都会附在最左侧叶子结点之后。
/*
* Locate the leftmost leaf query in the setops tree. The upper query's
* Vars all refer to this RTE (see transformSetOperationStmt).
*/
leftmostjtnode = topop->larg;
while (leftmostjtnode && IsA(leftmostjtnode, SetOperationStmt))
leftmostjtnode = ((SetOperationStmt*)leftmostjtnode)->larg;
找到最左侧的叶子结点之后,要进行两层的错误判断,首先leftmostjtnode应为RangeTblRef类型,其次其引用的RTE的类型应为RTE_SUBQUERY,否则函数中止,抛出err信息。
if (leftmostjtnode && IsA(leftmostjtnode, RangeTblRef)) {
leftmostRTI = ((RangeTblRef*)leftmostjtnode)->rtindex;
leftmostRTE = rt_fetch(leftmostRTI, parse->rtable);
AssertEreport(leftmostRTE->rtekind == RTE_SUBQUERY,
MOD_OPT_REWRITE,
"leftmostRTE should be SUBQUERY in flatten_simple_union_all");
} else {
ereport(ERROR,
(errmodule(MOD_OPT), errcode(ERRCODE_OPTIMIZER_INCONSISTENT_STATE), (errmsg("RangeTblRef not found."))));
}
把最左侧叶子结点引用的RTE加入range table,并让集合树setop tree引用这个新加入的RTE结点。
childRTE = (RangeTblEntry*)copyObject(leftmostRTE);
parse->rtable = lappend(parse->rtable, childRTE);
childRTI = list_length(parse->rtable);
/* Modify the setops tree to reference the child copy */
((RangeTblRef*)leftmostjtnode)->rtindex = childRTI;
接下来把这个RTE结点的inh字段置为true,表示其为一个追加关系中的起始结点。
/* Modify the formerly-leftmost RTE to mark it as an appendrel parent */
leftmostRTE->inh = true;
为当前层的查询结点的jointree字段构造fromlist结构。实际上就是让当前层查询结点的fromlist信息指向这个拉平后的追加关系表集。
/*
* Form a RangeTblRef for the appendrel, and insert it into FROM. The top
* Query of a setops tree should have had an empty FromClause initially.
*/
rtr = makeNode(RangeTblRef);
rtr->rtindex = leftmostRTI;
AssertEreport(parse->jointree->fromlist == NIL,
MOD_OPT_REWRITE,
"The top Query of a setops tree should have had an empty FromClause in flatten_simple_union_all");
parse->jointree->fromlist = list_make1(rtr);
接下来将setOperations字段置空,表示集合树已经被拉平,查询里已经不包括原本的树形集合结构了,但此时实际的拉平工作还未完成。
/*
* Now pretend the query has no setops. We must do this before trying to
* do subquery pullup, because of Assert in pull_up_simple_subquery.
*/
parse->setOperations = NULL;
最后调用函数 pull_up_union_leaf_queries() ,这个函数完成两个工作,一个是将各个叶子节点的RTE信息附加到fromlist中,构造附加关系;由于集合树的叶子结点引用的RTE类型应为RTE_SUBQUERY,因此另一个工作是调用pull_up_subqueries() 函数对各个叶子结点的子查询进行优化。
/*
* Build AppendRelInfo information, and apply pull_up_subqueries to the
* leaf queries of the UNION ALL. (We must do that now because they
* weren't previously referenced by the jointree, and so were missed by
* the main invocation of pull_up_subqueries.)
*/
pull_up_union_leaf_queries((Node*)topop, root, leftmostRTI, parse, 0);
函数 pull_up_union_leaf_queries()
接下来看函数 pull_up_union_leaf_queries(),它会为集合树中的每个叶子结点构造一个AppendRelInfo 结构,并且对叶子结点上的子查询应用 pull_up_subqueries() 函数进行优化。
static void pull_up_union_leaf_queries(
Node* setOp, PlannerInfo* root, int parentRTindex, Query* setOpQuery, int childRToffset)
对于 RangeTblRef 的结点,这类结点一定是 setop tree 上的叶子结点,为其构造相应的AppendRelInfo 结构,并添加到集合树的引用列表中。如前文所说,在上述操作结束后,调用 pull_up_subqueries() 函数对叶子结点RTE上的子查询进行优化。
if (IsA(setOp, RangeTblRef)) {
RangeTblRef* rtr = (RangeTblRef*)setOp;
int childRTindex;
AppendRelInfo* appinfo = NULL;
childRTindex = childRToffset + rtr->rtindex;
/*
* Build a suitable AppendRelInfo, and attach to parent's list.
*/
appinfo = makeNode(AppendRelInfo);
appinfo->parent_relid = parentRTindex;
appinfo->child_relid = childRTindex;
appinfo->parent_reltype = InvalidOid;
appinfo->child_reltype = InvalidOid;
make_setop_translation_list(setOpQuery, childRTindex, &appinfo->translated_vars);
appinfo->parent_reloid = InvalidOid;
root->append_rel_list = lappend(root->append_rel_list, appinfo);
/*
* Recursively apply pull_up_subqueries to the new child RTE. (We
* must build the AppendRelInfo first, because this will modify it.)
* Note that we can pass NULL for containing-join info even if we're
* actually under an outer join, because the child's expressions
* aren't going to propagate up above the join.
*/
rtr = makeNode(RangeTblRef);
rtr->rtindex = childRTindex;
(void)pull_up_subqueries_recurse(root, (Node*)rtr, NULL, NULL, appinfo);
}
对于 SetOperationStmt 类型的结点,其一定为 setop tree 中的内部结点,对其左右的子结点进行递归处理即可。在 setop tree 中,所有的结点或为SetOperationStmt类型(集合操作的顶层结点),或为RangeTblRef(RTE引用类型结点)。
else if (IsA(setOp, SetOperationStmt)) {
SetOperationStmt* op = (SetOperationStmt*)setOp;
/* Recurse to reach leaf queries */
pull_up_union_leaf_queries(op->larg, root, parentRTindex, setOpQuery, childRToffset);
pull_up_union_leaf_queries(op->rarg, root, parentRTindex, setOpQuery, childRToffset);
} else {
ereport(ERROR,
(errmodule(MOD_OPT),
errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE),
errmsg("unrecognized node type: %d", (int)nodeTag(setOp))));
}
总结
在本篇博客中,我对查询规划的预处理过程中对UNION ALL语句的优化过程进行了简单介绍。谢谢阅读。
更多推荐
所有评论(0)