diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2013-03-14 13:42:51 -0400 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2013-03-14 13:43:18 -0400 |
commit | 4387cf956b9eb13aad569634e0c4df081d76e2e3 (patch) | |
tree | c6a59e9e8010daa2b1865e06993f68599360adbe /src/backend | |
parent | a5ff502fceadc7c203b0d7a11b45c73f1b421f69 (diff) | |
download | postgresql-4387cf956b9eb13aad569634e0c4df081d76e2e3.tar.gz postgresql-4387cf956b9eb13aad569634e0c4df081d76e2e3.zip |
Avoid inserting Result nodes that only compute identity projections.
The planner sometimes inserts Result nodes to perform column projections
(ie, arbitrary scalar calculations) above plan nodes that lack projection
logic of their own. However, we did that even if the lower plan node was
in fact producing the required column set already; which is a pretty common
case given the popularity of "SELECT * FROM ...". Measurements show that
the useless plan node adds non-negligible overhead, especially when there
are many columns in the result. So add a check to avoid inserting a Result
node unless there's something useful for it to do.
There are a couple of remaining places where unnecessary Result nodes
could get inserted, but they are (a) much less performance-critical,
and (b) coded in such a way that it's hard to avoid inserting a Result,
because the desired tlist is changed on-the-fly in subsequent logic.
We'll leave those alone for now.
Kyotaro Horiguchi; reviewed and further hacked on by Amit Kapila and
Tom Lane.
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/optimizer/plan/createplan.c | 8 | ||||
-rw-r--r-- | src/backend/optimizer/plan/planner.c | 17 | ||||
-rw-r--r-- | src/backend/optimizer/util/tlist.c | 37 |
3 files changed, 53 insertions, 9 deletions
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c index d668128ccb1..65a2e38fc33 100644 --- a/src/backend/optimizer/plan/createplan.c +++ b/src/backend/optimizer/plan/createplan.c @@ -937,10 +937,12 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) if (newitems || best_path->umethod == UNIQUE_PATH_SORT) { /* - * If the top plan node can't do projections, we need to add a Result - * node to help it along. + * If the top plan node can't do projections and its existing target + * list isn't already what we need, we need to add a Result node to + * help it along. */ - if (!is_projection_capable_plan(subplan)) + if (!is_projection_capable_plan(subplan) && + !tlist_same_exprs(newtlist, subplan->targetlist)) subplan = (Plan *) make_result(root, newtlist, NULL, subplan); else subplan->targetlist = newtlist; diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index 0847e787c39..670776b032b 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -1379,10 +1379,12 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) { /* * If the top-level plan node is one that cannot do expression - * evaluation, we must insert a Result node to project the + * evaluation and its existing target list isn't already what + * we need, we must insert a Result node to project the * desired tlist. */ - if (!is_projection_capable_plan(result_plan)) + if (!is_projection_capable_plan(result_plan) && + !tlist_same_exprs(sub_tlist, result_plan->targetlist)) { result_plan = (Plan *) make_result(root, sub_tlist, @@ -1542,10 +1544,13 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) * If the top-level plan node is one that cannot do expression * evaluation, we must insert a Result node to project the desired * tlist. (In some cases this might not really be required, but - * it's not worth trying to avoid it.) Note that on second and - * subsequent passes through the following loop, the top-level - * node will be a WindowAgg which we know can project; so we only - * need to check once. + * it's not worth trying to avoid it. In particular, think not to + * skip adding the Result if the initial window_tlist matches the + * top-level plan node's output, because we might change the tlist + * inside the following loop.) Note that on second and subsequent + * passes through the following loop, the top-level node will be a + * WindowAgg which we know can project; so we only need to check + * once. */ if (!is_projection_capable_plan(result_plan)) { diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c index 9728ac8333b..5dc4b835fa3 100644 --- a/src/backend/optimizer/util/tlist.c +++ b/src/backend/optimizer/util/tlist.c @@ -159,6 +159,43 @@ get_tlist_exprs(List *tlist, bool includeJunk) /* + * tlist_same_exprs + * Check whether two target lists contain the same expressions + * + * Note: this function is used to decide whether it's safe to jam a new tlist + * into a non-projection-capable plan node. Obviously we can't do that unless + * the node's tlist shows it already returns the column values we want. + * However, we can ignore the TargetEntry attributes resname, ressortgroupref, + * resorigtbl, resorigcol, and resjunk, because those are only labelings that + * don't affect the row values computed by the node. (Moreover, if we didn't + * ignore them, we'd frequently fail to make the desired optimization, since + * the planner tends to not bother to make resname etc. valid in intermediate + * plan nodes.) Note that on success, the caller must still jam the desired + * tlist into the plan node, else it won't have the desired labeling fields. + */ +bool +tlist_same_exprs(List *tlist1, List *tlist2) +{ + ListCell *lc1, + *lc2; + + if (list_length(tlist1) != list_length(tlist2)) + return false; /* not same length, so can't match */ + + forboth(lc1, tlist1, lc2, tlist2) + { + TargetEntry *tle1 = (TargetEntry *) lfirst(lc1); + TargetEntry *tle2 = (TargetEntry *) lfirst(lc2); + + if (!equal(tle1->expr, tle2->expr)) + return false; + } + + return true; +} + + +/* * Does tlist have same output datatypes as listed in colTypes? * * Resjunk columns are ignored if junkOK is true; otherwise presence of |