本文简单介绍了PG查询优化中对顶层UNION ALL语句进行的扁平化(flatten)处理过程。扁平化处理的目的是把UNION ALL的集合操作转换为Append Relation以进行优化处理。
一、测试脚本
测试脚本:
testdb=# select a.dwbh
from t_dwxx a
union all
select b.dwbh
from t_grxx b
union all
select c.grbh
from t_jfxx c;
查询树如下图所示:
查询树
二、源码解读
/*
* flatten_simple_union_all
* Try to optimize top-level UNION ALL structure into an appendrel
*
* If a query's setOperations tree consists entirely of simple UNION ALL
* operations, flatten it into an append relation, which we can process more
* intelligently than the general setops case. Otherwise, do nothing.
*
* In most cases, this can succeed only for a top-level query, because for a
* subquery in FROM, the parent query's invocation of pull_up_subqueries would
* already have flattened the UNION via pull_up_simple_union_all. But there
* are a few cases we can support here but not in that code path, for example
* when the subquery also contains ORDER BY.
*/
void
flatten_simple_union_all(PlannerInfo *root)
{
Query *parse = root->parse;//查询树
SetOperationStmt *topop;//集合操作语句
Node *leftmostjtnode;//最左边节点
int leftmostRTI;//最左边节点对应的RTI(rtable中的位置,即rtindex)
RangeTblEntry *leftmostRTE;//最左边节点对应的RTE
int childRTI;//child的RTI
RangeTblEntry *childRTE;//子RTE
RangeTblRef *rtr;//RTR
/* Shouldn't be called unless query has setops */
topop = castNode(SetOperationStmt, parse->setOperations);//获取集合操作语句
Assert(topop);
/* Can't optimize away a recursive UNION */
if (root->hasRecursion)
return;//不支持递归UNION
/*
* Recursively check the tree of set operations. If not all UNION ALL
* with identical column types, punt.
*/
if (!is_simple_union_all_recurse((Node *) topop, parse, topop->colTypes))
return;//不是简单的UNION ALL,返回
/*
* Locate the leftmost leaf query in the setops tree. The upper query's
* Vars all refer to this RTE (see transformSetOperationStmt).
*/
leftmostjtnode = topop->larg;//左边的child
while (leftmostjtnode && IsA(leftmostjtnode, SetOperationStmt))
leftmostjtnode = ((SetOperationStmt *) leftmostjtnode)->larg;//获取最左边的叶子节点(非SetOperationStmt)
Assert(leftmostjtnode && IsA(leftmostjtnode, RangeTblRef));
leftmostRTI = ((RangeTblRef *) leftmostjtnode)->rtindex;//最左边节点的RTI
leftmostRTE = rt_fetch(leftmostRTI, parse->rtable);//从rtable中获取leftmostRTI指向的RTE
Assert(leftmostRTE->rtekind == RTE_SUBQUERY);//该RTE一定是子查询
/*
* Make a copy of the leftmost RTE and add it to the rtable. This copy
* will represent the leftmost leaf query in its capacity as a member of
* the appendrel. The original will represent the appendrel as a whole.
* (We must do things this way because the upper query's Vars have to be
* seen as referring to the whole appendrel.)
*/
childRTE = copyObject(leftmostRTE);//Make a copy
parse->rtable = lappend(parse->rtable, childRTE);//把RTE追加到上层rtable
childRTI = list_length(parse->rtable);//相应的rtindex
/* Modify the setops tree to reference the child copy */
((RangeTblRef *) leftmostjtnode)->rtindex = childRTI;//调整RTR的rtindex
/* Modify the formerly-leftmost RTE to mark it as an appendrel parent */
leftmostRTE->inh = true;
/*
* 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
rtr->rtindex = leftmostRTI;//rtindex=最左边节点的rtindex
Assert(parse->jointree->fromlist == NIL);//Query的jointree(FromExpr)->fromlist应为NULL
parse->jointree->fromlist = list_make1(rtr);//把RTR放到jointree的fromlist中
/*
* 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;//集合操作设置为NULL
/*
* 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 -- recursive guts of pull_up_simple_union_all
*
* Build an AppendRelInfo for each leaf query in the setop tree, and then
* apply pull_up_subqueries to the leaf query.
*
* Note that setOpQuery is the Query containing the setOp node, whose tlist
* contains references to all the setop output columns. When called from
* pull_up_simple_union_all, this is *not* the same as root->parse, which is
* the parent Query we are pulling up into.
*
* parentRTindex is the appendrel parent's index in root->parse->rtable.
*
* The child RTEs have already been copied to the parent. childRToffset
* tells us where in the parent's range table they were copied. When called
* from flatten_simple_union_all, childRToffset is 0 since the child RTEs
* were already in root->parse->rtable and no RT index adjustment is needed.
*/
static void
pull_up_union_leaf_queries(Node *setOp, PlannerInfo *root, int parentRTindex,
Query *setOpQuery, int childRToffset)
{
if (IsA(setOp, RangeTblRef))//RTR
{
RangeTblRef *rtr = (RangeTblRef *) setOp;
int childRTindex;
AppendRelInfo *appinfo;
/*
* Calculate the index in the parent's range table
*/
childRTindex = childRToffset + rtr->rtindex;
/*
* Build a suitable AppendRelInfo, and attach to parent's list.
*/
appinfo = makeNode(AppendRelInfo);//构造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);//把相关的Vars添加到AppendRelInfo中
appinfo->parent_reloid = InvalidOid;
root->append_rel_list = lappend(root->append_rel_list, appinfo);//添加到PlannerInfo中
/*
* 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 to the join. Also, we ignore the
* possibility that pull_up_subqueries_recurse() returns a different
* jointree node than what we pass it; if it does, the important thing
* is that it replaced the child relid in the AppendRelInfo node.
*/
rtr = makeNode(RangeTblRef);
rtr->rtindex = childRTindex;
(void) pull_up_subqueries_recurse(root, (Node *) rtr,
NULL, NULL, appinfo, false);//提升子查询
}
else if (IsA(setOp, SetOperationStmt))//类型为SetOperationStmt
{
SetOperationStmt *op = (SetOperationStmt *) setOp;
/* Recurse to reach leaf queries */
pull_up_union_leaf_queries(op->larg, root, parentRTindex, setOpQuery,
childRToffset);//左Child
pull_up_union_leaf_queries(op->rarg, root, parentRTindex, setOpQuery,
childRToffset);//右Child
}
else
{
elog(ERROR, "unrecognized node type: %d",
(int) nodeTag(setOp));
}
}
三、基础信息
数据结构/宏定义
1、SetOperationStmt
/* ----------------------
* Set Operation node for post-analysis query trees
*
* After parse analysis, a SELECT with set operations is represented by a
* top-level Query node containing the leaf SELECTs as subqueries in its
* range table. Its setOperations field shows the tree of set operations,
* with leaf SelectStmt nodes replaced by RangeTblRef nodes, and internal
* nodes replaced by SetOperationStmt nodes. Information about the output
* column types is added, too. (Note that the child nodes do not necessarily
* produce these types directly, but we've checked that their output types
* can be coerced to the output column type.) Also, if it's not UNION ALL,
* information about the types' sort/group semantics is provided in the form
* of a SortGroupClause list (same representation as, eg, DISTINCT).
* The resolved common column collations are provided too; but note that if
* it's not UNION ALL, it's okay for a column to not have a common collation,
* so a member of the colCollations list could be InvalidOid even though the
* column has a collatable type.
* ----------------------
*/
typedef enum SetOperation
{
SETOP_NONE = 0,
SETOP_UNION,
SETOP_INTERSECT,
SETOP_EXCEPT
} SetOperation;
typedef struct SetOperationStmt
{
NodeTag type;//节点类型
SetOperation op; /* 操作类型,type of set op */
bool all; /* 是否UNION ALL,ALL specified? */
Node *larg; /* left child,左Child,类型为RTR或SetOperationStmt */
Node *rarg; /* right child,右Child,类型为RTR或SetOperationStmt */
/* Eventually add fields for CORRESPONDING spec here */
/* Fields derived during parse analysis: */
List *colTypes; /* OID list of output column type OIDs,数据列类型 */
List *colTypmods; /* integer list of output column typmods,精度/刻度信息 */
List *colCollations; /* OID list of output column collation OIDs,collation信息*/
List *groupClauses; /* a list of SortGroupClause's,SortGroup语句*/
/* groupClauses is NIL if UNION ALL, but must be set otherwise */
} SetOperationStmt;
依赖的函数
1、make_setop_translation_list
/*
* make_setop_translation_list
* Build the list of translations from parent Vars to child Vars for
* a UNION ALL member. (At this point it's just a simple list of
* referencing Vars, but if we succeed in pulling up the member
* subquery, the Vars will get replaced by pulled-up expressions.)
*/
static void
make_setop_translation_list(Query *query, Index newvarno,
List **translated_vars)
{
List *vars = NIL;
ListCell *l;
foreach(l, query->targetList)
{
TargetEntry *tle = (TargetEntry *) lfirst(l);
if (tle->resjunk)
continue;
vars = lappend(vars, makeVarFromTargetEntry(newvarno, tle));
}
*translated_vars = vars;
}
四、跟踪分析
测试脚本见上,启动gdb跟踪:
(gdb) b flatten_simple_union_all
Breakpoint 1 at 0x77f964: file prepjointree.c, line 2355.
(gdb) c
Continuing.
Breakpoint 1, flatten_simple_union_all (root=0x16a6338) at prepjointree.c:2355
2355 Query *parse = root->parse;
#输入参数
#1.root,见前面章节,root->parse见先前小节的图(查询树)
...
#获取最左边的叶子节点(类型为RTR)
2384 while (leftmostjtnode && IsA(leftmostjtnode, SetOperationStmt))
(gdb)
2387 leftmostRTI = ((RangeTblRef *) leftmostjtnode)->rtindex;
(gdb) p *leftmostjtnode
$9 = {type = T_RangeTblRef}
...
#添加到root->rtable中
(gdb) n
2399 parse->rtable = lappend(parse->rtable, childRTE);
(gdb)
2400 childRTI = list_length(parse->rtable);
#原来的rtable为3个子查询,在最后面添加1个
(gdb) p *parse->rtable
$12 = {type = T_List, length = 4, head = 0x15e9dc8, tail = 0x16b09d0}
(gdb) p leftmostRTI
$19 = 1
#copy了一份"子查询",放在rtable的最后面
(gdb) p *((RangeTblEntry *)parse->rtable->head->next->next->next->data.ptr_value)->alias
$20 = {type = T_Alias, aliasname = 0x16b08d8 "*SELECT* 1", colnames = 0x0}
(gdb) p *((RangeTblEntry *)parse->rtable->head->data.ptr_value)->alias
$21 = {type = T_Alias, aliasname = 0x15e9cd0 "*SELECT* 1", colnames = 0x0}
(gdb)
#构造RTR(rtindex=1),放在jointree的fromlist中
2406 leftmostRTE->inh = true;
(gdb)
2412 rtr = makeNode(RangeTblRef);
(gdb)
2413 rtr->rtindex = leftmostRTI;
(gdb)
2415 parse->jointree->fromlist = list_make1(rtr);
(gdb) p *rtr
$23 = {type = T_RangeTblRef, rtindex = 1}
(gdb) p *(RangeTblRef *) leftmostjtnode
$24 = {type = T_RangeTblRef, rtindex = 4}
(gdb) p *parse->jointree->fromlist
$26 = {type = T_List, length = 1, head = 0x16b0a08, tail = 0x16b0a08}
#进入pull_up_union_leaf_queries
(gdb) n
2429 pull_up_union_leaf_queries((Node *) topop, root, leftmostRTI, parse, 0);
(gdb) step
pull_up_union_leaf_queries (setOp=0x15c59c8, root=0x16a6338, parentRTindex=1, setOpQuery=0x15c58b8, childRToffset=0)
at prepjointree.c:1344
1344 if (IsA(setOp, RangeTblRef))
#输入参数
#1.setOp,SetOperationStmt
#2.root,同flatten_simple_union_all
#3.leftmostRTI=1,最左边节点的rtindex
#4.parse,查询树
#5.childRToffset,RT的初始偏移量
1344 if (IsA(setOp, RangeTblRef))
#setOp为SetOperationStmt类型,递归调用SetOperationStmt->larg->larg
(gdb) n
1383 else if (IsA(setOp, SetOperationStmt))
...
1388 pull_up_union_leaf_queries(op->larg, root, parentRTindex, setOpQuery,
(gdb) step
pull_up_union_leaf_queries (setOp=0x15c5a18, root=0x16a6338, parentRTindex=1, setOpQuery=0x15c58b8, childRToffset=0)
at prepjointree.c:1344
1344 if (IsA(setOp, RangeTblRef))
1344 if (IsA(setOp, RangeTblRef))
(gdb) n
1383 else if (IsA(setOp, SetOperationStmt))
(gdb)
1385 SetOperationStmt *op = (SetOperationStmt *) setOp;
(gdb)
1388 pull_up_union_leaf_queries(op->larg, root, parentRTindex, setOpQuery,
(gdb) step
pull_up_union_leaf_queries (setOp=0x15e9e18, root=0x16a6338, parentRTindex=1, setOpQuery=0x15c58b8, childRToffset=0)
at prepjointree.c:1344
1344 if (IsA(setOp, RangeTblRef))
...
1346 RangeTblRef *rtr = (RangeTblRef *) setOp;
(gdb)
1353 childRTindex = childRToffset + rtr->rtindex;
(gdb)
1358 appinfo = makeNode(AppendRelInfo);
#最左边的RTR在flatten_simple_union_all中已调整为4
(gdb) p *rtr
$27 = {type = T_RangeTblRef, rtindex = 4}
(gdb) p childRTindex
$28 = 4
#构造AppendRelInfo,插入到root->append_rel_list中
(gdb) n
1359 appinfo->parent_relid = parentRTindex;
...
#进入pull_up_subqueries_recurse
(gdb) step
pull_up_subqueries_recurse (root=0x16a6338, jtnode=0x16b0b98, lowest_outer_join=0x0, lowest_nulling_outer_join=0x0,
containing_appendrel=0x16b0a58, deletion_ok=false) at prepjointree.c:680
680 if (IsA(jtnode, RangeTblRef))
#查询树的rtable中增加RTE(RTE_RELATION)
(gdb) p *parse->rtable
$47 = {type = T_List, length = 5, head = 0x15e9dc8, tail = 0x16b0cf0}
(gdb) p *(RangeTblEntry *)parse->rtable->tail->data.ptr_value
$49 = {type = T_RangeTblEntry, rtekind = RTE_RELATION, relid = 16394, relkind = 114 'r', tablesample = 0x0, subquery = 0x0,
security_barrier = false, jointype = JOIN_INNER, joinaliasvars = 0x0, functions = 0x0, funcordinality = false,
tablefunc = 0x0, values_lists = 0x0, ctename = 0x0, ctelevelsup = 0, self_reference = false, coltypes = 0x0,
coltypmods = 0x0, colcollations = 0x0, enrname = 0x0, enrtuples = 0, alias = 0x16b0e20, eref = 0x16b0e68,
lateral = false, inh = true, inFromCl = true, requiredPerms = 2, checkAsUser = 0, selectedCols = 0x16b0fe8,
insertedCols = 0x0, updatedCols = 0x0, securityQuals = 0x0}
(gdb) n
pull_up_subqueries_recurse (root=0x16a6338, jtnode=0x16b0b98, lowest_outer_join=0x0, lowest_nulling_outer_join=0x0,
containing_appendrel=0x16b0a58, deletion_ok=false) at prepjointree.c:853
853 }
(gdb)
pull_up_union_leaf_queries (setOp=0x15e9e18, root=0x16a6338, parentRTindex=1, setOpQuery=0x15c58b8, childRToffset=0)
at prepjointree.c:1398
1398 }
(gdb) n
#处理SetOperationStmt->larg(SetOperationStmt)->rarg
pull_up_union_leaf_queries (setOp=0x15c5a18, root=0x16a6338, parentRTindex=1, setOpQuery=0x15c58b8, childRToffset=0)
at prepjointree.c:1390
1390 pull_up_union_leaf_queries(op->rarg, root, parentRTindex, setOpQuery,
(gdb)
1398 }
(gdb)
#处理SetOperationStmt->rarg
pull_up_union_leaf_queries (setOp=0x15c59c8, root=0x16a6338, parentRTindex=1, setOpQuery=0x15c58b8, childRToffset=0)
at prepjointree.c:1390
1390 pull_up_union_leaf_queries(op->rarg, root, parentRTindex, setOpQuery,
(gdb)
1398 }
(gdb)
flatten_simple_union_all (root=0x16a6338) at prepjointree.c:2430
2430 }
#
(gdb) p *root
$54 = {type = T_PlannerInfo, parse = 0x15c58b8, glob = 0x15eb8b0, query_level = 1, parent_root = 0x0, plan_params = 0x0,
outer_params = 0x0, simple_rel_array = 0x0, simple_rel_array_size = 0, simple_rte_array = 0x0, all_baserels = 0x0,
nullable_baserels = 0x0, join_rel_list = 0x0, join_rel_hash = 0x0, join_rel_level = 0x0, join_cur_level = 0,
init_plans = 0x0, cte_plan_ids = 0x0, multiexpr_params = 0x0, eq_classes = 0x0, canon_pathkeys = 0x0,
left_join_clauses = 0x0, right_join_clauses = 0x0, full_join_clauses = 0x0, join_info_list = 0x0,
append_rel_list = 0x16b0b68, rowMarks = 0x0, placeholder_list = 0x0, fkey_list = 0x0, query_pathkeys = 0x0,
group_pathkeys = 0x0, window_pathkeys = 0x0, distinct_pathkeys = 0x0, sort_pathkeys = 0x0, part_schemes = 0x0,
initial_rels = 0x0, upper_rels = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, upper_targets = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0}, processed_tlist = 0x0, grouping_map = 0x0, minmax_aggs = 0x0, planner_cxt = 0x15c3e90, total_table_pages = 0,
tuple_fraction = 0, limit_tuples = 0, qual_security_level = 0, inhTargetKind = INHKIND_NONE, hasJoinRTEs = false,
hasLateralRTEs = false, hasDeletedRTEs = false, hasHavingQual = false, hasPseudoConstantQuals = false,
hasRecursion = false, wt_param_id = -1, non_recursive_path = 0x0, curOuterRels = 0x0, curOuterParams = 0x0,
join_search_private = 0x0, partColsUpdated = false}
(gdb) p *root->append_rel_list
$55 = {type = T_List, length = 3, head = 0x16b0b48, tail = 0x16b2790}
(gdb) p root->parse
$56 = (Query *) 0x15c58b8
(gdb) p *root->parse
$57 = {type = T_Query, commandType = CMD_SELECT, querySource = QSRC_ORIGINAL, queryId = 0, canSetTag = true,
utilityStmt = 0x0, resultRelation = 0, hasAggs = false, hasWindowFuncs = false, hasTargetSRFs = false,
hasSubLinks = false, hasDistinctOn = false, hasRecursive = false, hasModifyingCTE = false, hasForUpdate = false,
hasRowSecurity = false, cteList = 0x0, rtable = 0x15e9de8, jointree = 0x15eb880, targetList = 0x16b3348,
override = OVERRIDING_NOT_SET, onConflict = 0x0, returningList = 0x0, groupClause = 0x0, groupingSets = 0x0,
havingQual = 0x0, windowClause = 0x0, distinctClause = 0x0, sortClause = 0x0, limitOffset = 0x0, limitCount = 0x0,
rowMarks = 0x0, setOperations = 0x0, constraintDeps = 0x0, withCheckOptions = 0x0, stmt_location = 0, stmt_len = 104}
#rtable中已有7个元素,其中第4个是子查询,第5-7个是实际的RTE_RELATION(relid分别是16394/16397/16400)
(gdb) p *root->parse->rtable
$58 = {type = T_List, length = 7, head = 0x15e9dc8, tail = 0x16b2908}
(gdb)
#第4个元素
(gdb) p *((RangeTblEntry *)root->parse->rtable->head->next->next->next->data.ptr_value)
$80 = {type = T_RangeTblEntry, rtekind = RTE_SUBQUERY, relid = 0, relkind = 0 '\000', tablesample = 0x0,
subquery = 0x15c57a8, security_barrier = false, jointype = JOIN_INNER, joinaliasvars = 0x0, functions = 0x0,
funcordinality = false, tablefunc = 0x0, values_lists = 0x0, ctename = 0x0, ctelevelsup = 0, self_reference = false,
coltypes = 0x0, coltypmods = 0x0, colcollations = 0x0, enrname = 0x0, enrtuples = 0, alias = 0x16b08a8, eref = 0x16b08f8,
lateral = false, inh = false, inFromCl = false, requiredPerms = 0, checkAsUser = 0, selectedCols = 0x0,
insertedCols = 0x0, updatedCols = 0x0, securityQuals = 0x0}
(gdb) p *((RangeTblEntry *)root->parse->rtable->head->next->next->next->data.ptr_value)->eref
$81 = {type = T_Alias, aliasname = 0x16b0928 "*SELECT* 1", colnames = 0x16b0948}
#第5个元素
(gdb) p *((RangeTblEntry *)root->parse->rtable->head->next->next->next->next->data.ptr_value)
$82 = {type = T_RangeTblEntry, rtekind = RTE_RELATION, relid = 16394, relkind = 114 'r', tablesample = 0x0, subquery = 0x0,
security_barrier = false, jointype = JOIN_INNER, joinaliasvars = 0x0, functions = 0x0, funcordinality = false,
tablefunc = 0x0, values_lists = 0x0, ctename = 0x0, ctelevelsup = 0, self_reference = false, coltypes = 0x0,
coltypmods = 0x0, colcollations = 0x0, enrname = 0x0, enrtuples = 0, alias = 0x16b0e20, eref = 0x16b0e68,
lateral = false, inh = true, inFromCl = true, requiredPerms = 2, checkAsUser = 0, selectedCols = 0x16b0fe8,
insertedCols = 0x0, updatedCols = 0x0, securityQuals = 0x0}
(gdb) p *((RangeTblEntry *)root->parse->rtable->head->next->next->next->next->data.ptr_value)->eref
$83 = {type = T_Alias, aliasname = 0x16b0e98 "a", colnames = 0x16b0eb0}
#root->append_rel_list中有3个元素,分别对应rtable中的5-7
(gdb) p *root->append_rel_list
$69 = {type = T_List, length = 3, head = 0x16b0b48, tail = 0x16b2790}
(gdb) p *(Node *)root->append_rel_list->head->data.ptr_value
$70 = {type = T_AppendRelInfo}
(gdb) p *(AppendRelInfo *)root->append_rel_list->head->data.ptr_value
$71 = {type = T_AppendRelInfo, parent_relid = 1, child_relid = 5, parent_reltype = 0, child_reltype = 0,
translated_vars = 0x16b33e8, parent_reloid = 0}
#查询树的jointree
(gdb) p *root->parse->jointree
$87 = {type = T_FromExpr, fromlist = 0x16b0a28, quals = 0x0}
(gdb) p *root->parse->jointree->fromlist
$88 = {type = T_List, length = 1, head = 0x16b0a08, tail = 0x16b0a08}
(gdb) p *(RangeTblRef *)root->parse->jointree->fromlist->head->data.ptr_value
$90 = {type = T_RangeTblRef, rtindex = 1}
(gdb) n
subquery_planner (glob=0x15eb8b0, parse=0x15c58b8, parent_root=0x0, hasRecursion=false, tuple_fraction=0) at planner.c:680
680 root->hasJoinRTEs = false;
#DONE!
优化后的查询树如下图所示:
查询树(优化后)
五、小结
UNION ALL优化过程:把集合操作扁平化为AppendRelInfo,通过增加AppendRelInfo、修改rtable等方式实现。
参考资料:
prepjointree.c
list.c
网友评论