1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-27 23:21:58 +03:00

Fix best_inner_indexscan to return both the cheapest-total-cost and

cheapest-startup-cost innerjoin indexscans, and make joinpath.c consider
both of these (when different) as the inside of a nestloop join.  The
original design was based on the assumption that indexscan paths always
have negligible startup cost, and so total cost is the only important
figure of merit; an assumption that's obviously broken by bitmap
indexscans.  This oversight could lead to choosing poor plans in cases
where fast-start behavior is more important than total cost, such as
LIMIT and IN queries.  8.1-vintage brain fade exposed by an example from
Chuck D.
This commit is contained in:
Tom Lane
2007-05-22 01:40:33 +00:00
parent 2415ad9831
commit d7153c5fad
5 changed files with 94 additions and 63 deletions

View File

@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/optimizer/path/joinpath.c,v 1.111 2007/01/20 20:45:39 tgl Exp $
* $PostgreSQL: pgsql/src/backend/optimizer/path/joinpath.c,v 1.112 2007/05/22 01:40:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@ -286,10 +286,11 @@ sort_inner_and_outer(PlannerInfo *root,
* only outer paths that are already ordered well enough for merging).
*
* We always generate a nestloop path for each available outer path.
* In fact we may generate as many as four: one on the cheapest-total-cost
* In fact we may generate as many as five: one on the cheapest-total-cost
* inner path, one on the same with materialization, one on the
* cheapest-startup-cost inner path (if different),
* and one on the best inner-indexscan path (if any).
* cheapest-startup-cost inner path (if different), one on the
* cheapest-total inner-indexscan path (if any), and one on the
* cheapest-startup inner-indexscan path (if different).
*
* We also consider mergejoins if mergejoin clauses are available. We have
* two ways to generate the inner path for a mergejoin: sort the cheapest
@ -325,7 +326,8 @@ match_unsorted_outer(PlannerInfo *root,
Path *inner_cheapest_startup = innerrel->cheapest_startup_path;
Path *inner_cheapest_total = innerrel->cheapest_total_path;
Path *matpath = NULL;
Path *bestinnerjoin = NULL;
Path *index_cheapest_startup = NULL;
Path *index_cheapest_total = NULL;
ListCell *l;
/*
@ -383,17 +385,20 @@ match_unsorted_outer(PlannerInfo *root,
create_material_path(innerrel, inner_cheapest_total);
/*
* Get the best innerjoin indexpath (if any) for this outer rel. It's
* the same for all outer paths.
* Get the best innerjoin indexpaths (if any) for this outer rel.
* They're the same for all outer paths.
*/
if (innerrel->reloptkind != RELOPT_JOINREL)
{
if (IsA(inner_cheapest_total, AppendPath))
bestinnerjoin = best_appendrel_indexscan(root, innerrel,
outerrel, jointype);
index_cheapest_total = best_appendrel_indexscan(root,
innerrel,
outerrel,
jointype);
else if (innerrel->rtekind == RTE_RELATION)
bestinnerjoin = best_inner_indexscan(root, innerrel,
outerrel, jointype);
best_inner_indexscan(root, innerrel, outerrel, jointype,
&index_cheapest_startup,
&index_cheapest_total);
}
}
@ -435,8 +440,8 @@ match_unsorted_outer(PlannerInfo *root,
* Always consider a nestloop join with this outer and
* cheapest-total-cost inner. When appropriate, also consider
* using the materialized form of the cheapest inner, the
* cheapest-startup-cost inner path, and the best innerjoin
* indexpath.
* cheapest-startup-cost inner path, and the cheapest innerjoin
* indexpaths.
*/
add_path(joinrel, (Path *)
create_nestloop_path(root,
@ -464,13 +469,23 @@ match_unsorted_outer(PlannerInfo *root,
inner_cheapest_startup,
restrictlist,
merge_pathkeys));
if (bestinnerjoin != NULL)
if (index_cheapest_total != NULL)
add_path(joinrel, (Path *)
create_nestloop_path(root,
joinrel,
jointype,
outerpath,
bestinnerjoin,
index_cheapest_total,
restrictlist,
merge_pathkeys));
if (index_cheapest_startup != NULL &&
index_cheapest_startup != index_cheapest_total)
add_path(joinrel, (Path *)
create_nestloop_path(root,
joinrel,
jointype,
outerpath,
index_cheapest_startup,
restrictlist,
merge_pathkeys));
}
@ -789,6 +804,9 @@ hash_inner_and_outer(PlannerInfo *root,
* with the given append relation on the inside and the given outer_rel
* outside. Returns an AppendPath comprising the best inner scans, or
* NULL if there are no possible inner indexscans.
*
* Note that we currently consider only cheapest-total-cost. It's not
* very clear what cheapest-startup-cost might mean for an AppendPath.
*/
static Path *
best_appendrel_indexscan(PlannerInfo *root, RelOptInfo *rel,
@ -804,7 +822,8 @@ best_appendrel_indexscan(PlannerInfo *root, RelOptInfo *rel,
AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(l);
int childRTindex;
RelOptInfo *childrel;
Path *bestinnerjoin;
Path *index_cheapest_startup;
Path *index_cheapest_total;
/* append_rel_list contains all append rels; ignore others */
if (appinfo->parent_relid != parentRTindex)
@ -824,10 +843,10 @@ best_appendrel_indexscan(PlannerInfo *root, RelOptInfo *rel,
continue; /* OK, we can ignore it */
/*
* Get the best innerjoin indexpath (if any) for this child rel.
* Get the best innerjoin indexpaths (if any) for this child rel.
*/
bestinnerjoin = best_inner_indexscan(root, childrel,
outer_rel, jointype);
best_inner_indexscan(root, childrel, outer_rel, jointype,
&index_cheapest_startup, &index_cheapest_total);
/*
* If no luck on an indexpath for this rel, we'll still consider an
@ -835,12 +854,12 @@ best_appendrel_indexscan(PlannerInfo *root, RelOptInfo *rel,
* find at least one indexpath, else there's not going to be any
* improvement over the base path for the appendrel.
*/
if (bestinnerjoin)
if (index_cheapest_total)
found_indexscan = true;
else
bestinnerjoin = childrel->cheapest_total_path;
index_cheapest_total = childrel->cheapest_total_path;
append_paths = lappend(append_paths, bestinnerjoin);
append_paths = lappend(append_paths, index_cheapest_total);
}
if (!found_indexscan)