Improve planner's ability to recognize cases where an IN's RHS is unique.

If the right-hand side of a semijoin is unique, then we can treat it like a
normal join (or another way to say that is: we don't need to explicitly
unique-ify the data before doing it as a normal join).  We were recognizing
such cases when the RHS was a sub-query with appropriate DISTINCT or GROUP
BY decoration, but there's another way: if the RHS is a plain relation with
unique indexes, we can check if any of the indexes prove the output is
unique.  Most of the infrastructure for that was there already in the join
removal code, though I had to rearrange it a bit.  Per reflection about a
recent example in pgsql-performance.
This commit is contained in:
Tom Lane 2011-10-26 17:52:02 -04:00
parent 360429e1d1
commit 3e4b3465b6
4 changed files with 134 additions and 49 deletions

View File

@ -2253,21 +2253,74 @@ find_clauses_for_join(PlannerInfo *root, RelOptInfo *rel,
* a set of equality conditions, because the conditions constrain all
* columns of some unique index.
*
* The conditions are provided as a list of RestrictInfo nodes, where the
* caller has already determined that each condition is a mergejoinable
* equality with an expression in this relation on one side, and an
* expression not involving this relation on the other. The transient
* outer_is_left flag is used to identify which side we should look at:
* left side if outer_is_left is false, right side if it is true.
* The conditions can be represented in either or both of two ways:
* 1. A list of RestrictInfo nodes, where the caller has already determined
* that each condition is a mergejoinable equality with an expression in
* this relation on one side, and an expression not involving this relation
* on the other. The transient outer_is_left flag is used to identify which
* side we should look at: left side if outer_is_left is false, right side
* if it is true.
* 2. A list of expressions in this relation, and a corresponding list of
* equality operators. The caller must have already checked that the operators
* represent equality. (Note: the operators could be cross-type; the
* expressions should correspond to their RHS inputs.)
*
* The caller need only supply equality conditions arising from joins;
* this routine automatically adds in any usable baserestrictinfo clauses.
* (Note that the passed-in restrictlist will be destructively modified!)
*/
bool
relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
List *restrictlist)
List *restrictlist,
List *exprlist, List *oprlist)
{
ListCell *ic;
Assert(list_length(exprlist) == list_length(oprlist));
/* Short-circuit if no indexes... */
if (rel->indexlist == NIL)
return false;
/*
* Examine the rel's restriction clauses for usable var = const clauses
* that we can add to the restrictlist.
*/
foreach(ic, rel->baserestrictinfo)
{
RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(ic);
/*
* Note: can_join won't be set for a restriction clause, but
* mergeopfamilies will be if it has a mergejoinable operator and
* doesn't contain volatile functions.
*/
if (restrictinfo->mergeopfamilies == NIL)
continue; /* not mergejoinable */
/*
* The clause certainly doesn't refer to anything but the given rel.
* If either side is pseudoconstant then we can use it.
*/
if (bms_is_empty(restrictinfo->left_relids))
{
/* righthand side is inner */
restrictinfo->outer_is_left = true;
}
else if (bms_is_empty(restrictinfo->right_relids))
{
/* lefthand side is inner */
restrictinfo->outer_is_left = false;
}
else
continue;
/* OK, add to list */
restrictlist = lappend(restrictlist, restrictinfo);
}
/* Short-circuit the easy case */
if (restrictlist == NIL)
if (restrictlist == NIL && exprlist == NIL)
return false;
/* Examine each index of the relation ... */
@ -2285,12 +2338,14 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
continue;
/*
* Try to find each index column in the list of conditions. This is
* O(n^2) or worse, but we expect all the lists to be short.
* Try to find each index column in the lists of conditions. This is
* O(N^2) or worse, but we expect all the lists to be short.
*/
for (c = 0; c < ind->ncolumns; c++)
{
bool matched = false;
ListCell *lc;
ListCell *lc2;
foreach(lc, restrictlist)
{
@ -2319,10 +2374,45 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
rexpr = get_leftop(rinfo->clause);
if (match_index_to_operand(rexpr, c, ind))
break; /* found a match; column is unique */
{
matched = true; /* column is unique */
break;
}
}
if (lc == NULL)
if (matched)
continue;
forboth(lc, exprlist, lc2, oprlist)
{
Node *expr = (Node *) lfirst(lc);
Oid opr = lfirst_oid(lc2);
/* See if the expression matches the index key */
if (!match_index_to_operand(expr, c, ind))
continue;
/*
* The equality operator must be a member of the index
* opfamily, else it is not asserting the right kind of
* equality behavior for this index. We assume the caller
* determined it is an equality operator, so we don't need to
* check any more tightly than this.
*/
if (!op_in_opfamily(opr, ind->opfamily[c]))
continue;
/*
* XXX at some point we may need to check collations here too.
* For the moment we assume all collations reduce to the same
* notion of equality.
*/
matched = true; /* column is unique */
break;
}
if (!matched)
break; /* no match; this index doesn't help us */
}

View File

@ -264,42 +264,13 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo)
clause_list = lappend(clause_list, restrictinfo);
}
/* Now examine the rel's restriction clauses for var = const clauses */
foreach(l, innerrel->baserestrictinfo)
{
RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l);
/*
* Note: can_join won't be set for a restriction clause, but
* mergeopfamilies will be if it has a mergejoinable operator and
* doesn't contain volatile functions.
*/
if (restrictinfo->mergeopfamilies == NIL)
continue; /* not mergejoinable */
/*
* The clause certainly doesn't refer to anything but the given rel.
* If either side is pseudoconstant then we can use it.
*/
if (bms_is_empty(restrictinfo->left_relids))
{
/* righthand side is inner */
restrictinfo->outer_is_left = true;
}
else if (bms_is_empty(restrictinfo->right_relids))
{
/* lefthand side is inner */
restrictinfo->outer_is_left = false;
}
else
continue;
/* OK, add to list */
clause_list = lappend(clause_list, restrictinfo);
}
/*
* relation_has_unique_index_for automatically adds any usable restriction
* clauses for the innerrel, so we needn't do that here.
*/
/* Now examine the indexes to see if we have a matching unique index */
if (relation_has_unique_index_for(root, innerrel, clause_list))
if (relation_has_unique_index_for(root, innerrel, clause_list, NIL, NIL))
return true;
/*

View File

@ -1021,8 +1021,8 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
pathnode->path.parent = rel;
/*
* Treat the output as always unsorted, since we don't necessarily have
* pathkeys to represent it.
* Assume the output is unsorted, since we don't necessarily have pathkeys
* to represent it. (This might get overridden below.)
*/
pathnode->path.pathkeys = NIL;
@ -1030,6 +1030,29 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
pathnode->in_operators = in_operators;
pathnode->uniq_exprs = uniq_exprs;
/*
* If the input is a relation and it has a unique index that proves the
* uniq_exprs are unique, then we don't need to do anything. Note that
* relation_has_unique_index_for automatically considers restriction
* clauses for the rel, as well.
*/
if (rel->rtekind == RTE_RELATION && all_btree &&
relation_has_unique_index_for(root, rel, NIL,
uniq_exprs, in_operators))
{
pathnode->umethod = UNIQUE_PATH_NOOP;
pathnode->rows = rel->rows;
pathnode->path.startup_cost = subpath->startup_cost;
pathnode->path.total_cost = subpath->total_cost;
pathnode->path.pathkeys = subpath->pathkeys;
rel->cheapest_unique_path = (Path *) pathnode;
MemoryContextSwitchTo(oldcontext);
return pathnode;
}
/*
* If the input is a subquery whose output must be unique already, then we
* don't need to do anything. The test for uniqueness has to consider

View File

@ -50,7 +50,8 @@ extern void best_inner_indexscan(PlannerInfo *root, RelOptInfo *rel,
RelOptInfo *outer_rel, JoinType jointype,
Path **cheapest_startup, Path **cheapest_total);
extern bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
List *restrictlist);
List *restrictlist,
List *exprlist, List *oprlist);
extern bool eclass_matches_any_index(EquivalenceClass *ec,
EquivalenceMember *em,
RelOptInfo *rel);