Commit 4387cf95 authored by Tom Lane's avatar Tom Lane

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.
parent a5ff502f
...@@ -937,10 +937,12 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path) ...@@ -937,10 +937,12 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path)
if (newitems || best_path->umethod == UNIQUE_PATH_SORT) if (newitems || best_path->umethod == UNIQUE_PATH_SORT)
{ {
/* /*
* If the top plan node can't do projections, we need to add a Result * If the top plan node can't do projections and its existing target
* node to help it along. * 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); subplan = (Plan *) make_result(root, newtlist, NULL, subplan);
else else
subplan->targetlist = newtlist; subplan->targetlist = newtlist;
......
...@@ -1379,10 +1379,12 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) ...@@ -1379,10 +1379,12 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
{ {
/* /*
* If the top-level plan node is one that cannot do expression * 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. * 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, result_plan = (Plan *) make_result(root,
sub_tlist, sub_tlist,
...@@ -1542,10 +1544,13 @@ grouping_planner(PlannerInfo *root, double tuple_fraction) ...@@ -1542,10 +1544,13 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
* If the top-level plan node is one that cannot do expression * If the top-level plan node is one that cannot do expression
* evaluation, we must insert a Result node to project the desired * evaluation, we must insert a Result node to project the desired
* tlist. (In some cases this might not really be required, but * tlist. (In some cases this might not really be required, but
* it's not worth trying to avoid it.) Note that on second and * it's not worth trying to avoid it. In particular, think not to
* subsequent passes through the following loop, the top-level * skip adding the Result if the initial window_tlist matches the
* node will be a WindowAgg which we know can project; so we only * top-level plan node's output, because we might change the tlist
* need to check once. * 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)) if (!is_projection_capable_plan(result_plan))
{ {
......
...@@ -158,6 +158,43 @@ get_tlist_exprs(List *tlist, bool includeJunk) ...@@ -158,6 +158,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? * Does tlist have same output datatypes as listed in colTypes?
* *
......
...@@ -25,6 +25,9 @@ extern List *flatten_tlist(List *tlist, PVCAggregateBehavior aggbehavior, ...@@ -25,6 +25,9 @@ extern List *flatten_tlist(List *tlist, PVCAggregateBehavior aggbehavior,
extern List *add_to_flat_tlist(List *tlist, List *exprs); extern List *add_to_flat_tlist(List *tlist, List *exprs);
extern List *get_tlist_exprs(List *tlist, bool includeJunk); extern List *get_tlist_exprs(List *tlist, bool includeJunk);
extern bool tlist_same_exprs(List *tlist1, List *tlist2);
extern bool tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK); extern bool tlist_same_datatypes(List *tlist, List *colTypes, bool junkOK);
extern bool tlist_same_collations(List *tlist, List *colCollations, bool junkOK); extern bool tlist_same_collations(List *tlist, List *colCollations, bool junkOK);
......
...@@ -385,61 +385,57 @@ create table nv_child_2011 () inherits (nv_parent); ...@@ -385,61 +385,57 @@ create table nv_child_2011 () inherits (nv_parent);
alter table nv_child_2010 add check (d between '2010-01-01'::date and '2010-12-31'::date) not valid; alter table nv_child_2010 add check (d between '2010-01-01'::date and '2010-12-31'::date) not valid;
alter table nv_child_2011 add check (d between '2011-01-01'::date and '2011-12-31'::date) not valid; alter table nv_child_2011 add check (d between '2011-01-01'::date and '2011-12-31'::date) not valid;
explain (costs off) select * from nv_parent where d between '2011-08-01' and '2011-08-31'; explain (costs off) select * from nv_parent where d between '2011-08-01' and '2011-08-31';
QUERY PLAN QUERY PLAN
--------------------------------------------------------------------------------- ---------------------------------------------------------------------------
Result Append
-> Append -> Seq Scan on nv_parent
-> Seq Scan on nv_parent Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date)) -> Seq Scan on nv_child_2010
-> Seq Scan on nv_child_2010 Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date)) -> Seq Scan on nv_child_2011
-> Seq Scan on nv_child_2011 Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date)) (7 rows)
(8 rows)
create table nv_child_2009 (check (d between '2009-01-01'::date and '2009-12-31'::date)) inherits (nv_parent); create table nv_child_2009 (check (d between '2009-01-01'::date and '2009-12-31'::date)) inherits (nv_parent);
explain (costs off) select * from nv_parent where d between '2011-08-01'::date and '2011-08-31'::date; explain (costs off) select * from nv_parent where d between '2011-08-01'::date and '2011-08-31'::date;
QUERY PLAN QUERY PLAN
--------------------------------------------------------------------------------- ---------------------------------------------------------------------------
Result Append
-> Append -> Seq Scan on nv_parent
-> Seq Scan on nv_parent Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date)) -> Seq Scan on nv_child_2010
-> Seq Scan on nv_child_2010 Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date)) -> Seq Scan on nv_child_2011
-> Seq Scan on nv_child_2011 Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date))
Filter: ((d >= '08-01-2011'::date) AND (d <= '08-31-2011'::date)) (7 rows)
(8 rows)
explain (costs off) select * from nv_parent where d between '2009-08-01'::date and '2009-08-31'::date; explain (costs off) select * from nv_parent where d between '2009-08-01'::date and '2009-08-31'::date;
QUERY PLAN QUERY PLAN
--------------------------------------------------------------------------------- ---------------------------------------------------------------------------
Result Append
-> Append -> Seq Scan on nv_parent
-> Seq Scan on nv_parent Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date)) -> Seq Scan on nv_child_2010
-> Seq Scan on nv_child_2010 Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date)) -> Seq Scan on nv_child_2011
-> Seq Scan on nv_child_2011 Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date)) -> Seq Scan on nv_child_2009
-> Seq Scan on nv_child_2009 Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date)) (9 rows)
(10 rows)
-- after validation, the constraint should be used -- after validation, the constraint should be used
alter table nv_child_2011 VALIDATE CONSTRAINT nv_child_2011_d_check; alter table nv_child_2011 VALIDATE CONSTRAINT nv_child_2011_d_check;
explain (costs off) select * from nv_parent where d between '2009-08-01'::date and '2009-08-31'::date; explain (costs off) select * from nv_parent where d between '2009-08-01'::date and '2009-08-31'::date;
QUERY PLAN QUERY PLAN
--------------------------------------------------------------------------------- ---------------------------------------------------------------------------
Result Append
-> Append -> Seq Scan on nv_parent
-> Seq Scan on nv_parent Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date)) -> Seq Scan on nv_child_2010
-> Seq Scan on nv_child_2010 Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date)) -> Seq Scan on nv_child_2009
-> Seq Scan on nv_child_2009 Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date))
Filter: ((d >= '08-01-2009'::date) AND (d <= '08-31-2009'::date)) (7 rows)
(8 rows)
-- Foreign key adding test with mixed types -- Foreign key adding test with mixed types
-- Note: these tables are TEMP to avoid name conflicts when this test -- Note: these tables are TEMP to avoid name conflicts when this test
......
...@@ -1210,24 +1210,22 @@ select * from matest0 order by 1-id; ...@@ -1210,24 +1210,22 @@ select * from matest0 order by 1-id;
reset enable_indexscan; reset enable_indexscan;
set enable_seqscan = off; -- plan with fewest seqscans should be merge set enable_seqscan = off; -- plan with fewest seqscans should be merge
explain (verbose, costs off) select * from matest0 order by 1-id; explain (verbose, costs off) select * from matest0 order by 1-id;
QUERY PLAN QUERY PLAN
------------------------------------------------------------------------ ------------------------------------------------------------------
Result Merge Append
Output: matest0.id, matest0.name, ((1 - matest0.id)) Sort Key: ((1 - matest0.id))
-> Merge Append -> Index Scan using matest0i on public.matest0
Sort Key: ((1 - matest0.id)) Output: matest0.id, matest0.name, (1 - matest0.id)
-> Index Scan using matest0i on public.matest0 -> Index Scan using matest1i on public.matest1
Output: matest0.id, matest0.name, (1 - matest0.id) Output: matest1.id, matest1.name, (1 - matest1.id)
-> Index Scan using matest1i on public.matest1 -> Sort
Output: matest1.id, matest1.name, (1 - matest1.id) Output: matest2.id, matest2.name, ((1 - matest2.id))
-> Sort Sort Key: ((1 - matest2.id))
Output: matest2.id, matest2.name, ((1 - matest2.id)) -> Seq Scan on public.matest2
Sort Key: ((1 - matest2.id)) Output: matest2.id, matest2.name, (1 - matest2.id)
-> Seq Scan on public.matest2 -> Index Scan using matest3i on public.matest3
Output: matest2.id, matest2.name, (1 - matest2.id) Output: matest3.id, matest3.name, (1 - matest3.id)
-> Index Scan using matest3i on public.matest3 (13 rows)
Output: matest3.id, matest3.name, (1 - matest3.id)
(15 rows)
select * from matest0 order by 1-id; select * from matest0 order by 1-id;
id | name id | name
...@@ -1258,48 +1256,45 @@ SELECT thousand, tenthous FROM tenk1 ...@@ -1258,48 +1256,45 @@ SELECT thousand, tenthous FROM tenk1
UNION ALL UNION ALL
SELECT thousand, thousand FROM tenk1 SELECT thousand, thousand FROM tenk1
ORDER BY thousand, tenthous; ORDER BY thousand, tenthous;
QUERY PLAN QUERY PLAN
------------------------------------------------------------------------------- -------------------------------------------------------------------------
Result Merge Append
-> Merge Append Sort Key: tenk1.thousand, tenk1.tenthous
Sort Key: tenk1.thousand, tenk1.tenthous -> Index Only Scan using tenk1_thous_tenthous on tenk1
-> Index Only Scan using tenk1_thous_tenthous on tenk1 -> Sort
-> Sort Sort Key: tenk1_1.thousand, tenk1_1.thousand
Sort Key: tenk1_1.thousand, tenk1_1.thousand -> Index Only Scan using tenk1_thous_tenthous on tenk1 tenk1_1
-> Index Only Scan using tenk1_thous_tenthous on tenk1 tenk1_1 (6 rows)
(7 rows)
explain (costs off) explain (costs off)
SELECT thousand, tenthous, thousand+tenthous AS x FROM tenk1 SELECT thousand, tenthous, thousand+tenthous AS x FROM tenk1
UNION ALL UNION ALL
SELECT 42, 42, hundred FROM tenk1 SELECT 42, 42, hundred FROM tenk1
ORDER BY thousand, tenthous; ORDER BY thousand, tenthous;
QUERY PLAN QUERY PLAN
------------------------------------------------------------------------ ------------------------------------------------------------------
Result Merge Append
-> Merge Append Sort Key: tenk1.thousand, tenk1.tenthous
Sort Key: tenk1.thousand, tenk1.tenthous -> Index Only Scan using tenk1_thous_tenthous on tenk1
-> Index Only Scan using tenk1_thous_tenthous on tenk1 -> Sort
-> Sort Sort Key: (42), (42)
Sort Key: (42), (42) -> Index Only Scan using tenk1_hundred on tenk1 tenk1_1
-> Index Only Scan using tenk1_hundred on tenk1 tenk1_1 (6 rows)
(7 rows)
explain (costs off) explain (costs off)
SELECT thousand, tenthous FROM tenk1 SELECT thousand, tenthous FROM tenk1
UNION ALL UNION ALL
SELECT thousand, random()::integer FROM tenk1 SELECT thousand, random()::integer FROM tenk1
ORDER BY thousand, tenthous; ORDER BY thousand, tenthous;
QUERY PLAN QUERY PLAN
------------------------------------------------------------------------------- -------------------------------------------------------------------------
Result Merge Append
-> Merge Append Sort Key: tenk1.thousand, tenk1.tenthous
Sort Key: tenk1.thousand, tenk1.tenthous -> Index Only Scan using tenk1_thous_tenthous on tenk1
-> Index Only Scan using tenk1_thous_tenthous on tenk1 -> Sort
-> Sort Sort Key: tenk1_1.thousand, ((random())::integer)
Sort Key: tenk1_1.thousand, ((random())::integer) -> Index Only Scan using tenk1_thous_tenthous on tenk1 tenk1_1
-> Index Only Scan using tenk1_thous_tenthous on tenk1 tenk1_1 (6 rows)
(7 rows)
-- Check min/max aggregate optimization -- Check min/max aggregate optimization
explain (costs off) explain (costs off)
...@@ -1345,16 +1340,15 @@ SELECT x, y FROM ...@@ -1345,16 +1340,15 @@ SELECT x, y FROM
UNION ALL UNION ALL
SELECT unique2 AS x, unique2 AS y FROM tenk1 b) s SELECT unique2 AS x, unique2 AS y FROM tenk1 b) s
ORDER BY x, y; ORDER BY x, y;
QUERY PLAN QUERY PLAN
------------------------------------------------------------------- -------------------------------------------------------------
Result Merge Append
-> Merge Append Sort Key: a.thousand, a.tenthous
Sort Key: a.thousand, a.tenthous -> Index Only Scan using tenk1_thous_tenthous on tenk1 a
-> Index Only Scan using tenk1_thous_tenthous on tenk1 a -> Sort
-> Sort Sort Key: b.unique2, b.unique2
Sort Key: b.unique2, b.unique2 -> Index Only Scan using tenk1_unique2 on tenk1 b
-> Index Only Scan using tenk1_unique2 on tenk1 b (6 rows)
(7 rows)
reset enable_seqscan; reset enable_seqscan;
reset enable_indexscan; reset enable_indexscan;
......
...@@ -474,15 +474,14 @@ explain (costs off) ...@@ -474,15 +474,14 @@ explain (costs off)
UNION ALL UNION ALL
SELECT * FROM t2) t SELECT * FROM t2) t
WHERE ab = 'ab'; WHERE ab = 'ab';
QUERY PLAN QUERY PLAN
--------------------------------------------------- ---------------------------------------------
Result Append
-> Append -> Index Scan using t1_ab_idx on t1
-> Index Scan using t1_ab_idx on t1 Index Cond: ((a || b) = 'ab'::text)
Index Cond: ((a || b) = 'ab'::text) -> Index Only Scan using t2_pkey on t2
-> Index Only Scan using t2_pkey on t2 Index Cond: (ab = 'ab'::text)
Index Cond: (ab = 'ab'::text) (5 rows)
(6 rows)
explain (costs off) explain (costs off)
SELECT * FROM SELECT * FROM
...@@ -510,10 +509,9 @@ explain (costs off) ...@@ -510,10 +509,9 @@ explain (costs off)
UNION ALL UNION ALL
SELECT 2 AS t, * FROM tenk1 b) c SELECT 2 AS t, * FROM tenk1 b) c
WHERE t = 2; WHERE t = 2;
QUERY PLAN QUERY PLAN
--------------------------------- ---------------------------
Result Append
-> Append -> Seq Scan on tenk1 b
-> Seq Scan on tenk1 b (2 rows)
(3 rows)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment