mirror of
https://github.com/postgres/postgres.git
synced 2025-07-02 09:02:37 +03:00
Improve planner's cost estimation in the presence of semijoins.
If we have a semijoin, say SELECT * FROM x WHERE x1 IN (SELECT y1 FROM y) and we're estimating the cost of a parameterized indexscan on x, the number of repetitions of the indexscan should not be taken as the size of y; it'll really only be the number of distinct values of y1, because the only valid plan with y on the outside of a nestloop would require y to be unique-ified before joining it to x. Most of the time this doesn't make that much difference, but sometimes it can lead to drastically underestimating the cost of the indexscan and hence choosing a bad plan, as pointed out by David Kubečka. Fixing this is a bit difficult because parameterized indexscans are costed out quite early in the planning process, before we have the information that would be needed to call estimate_num_groups() and thereby estimate the number of distinct values of the join column(s). However we can move the code that extracts a semijoin RHS's unique-ification columns, so that it's done in initsplan.c rather than on-the-fly in create_unique_path(). That shouldn't make any difference speed-wise and it's really a bit cleaner too. The other bit of information we need is the size of the semijoin RHS, which is easy if it's a single relation (we make those estimates before considering indexscan costs) but problematic if it's a join relation. The solution adopted here is just to use the product of the sizes of the join component rels. That will generally be an overestimate, but since estimate_num_groups() only uses this input as a clamp, an overestimate shouldn't hurt us too badly. In any case we don't allow this new logic to produce a value larger than we would have chosen before, so that at worst an overestimate leaves us no wiser than we were before.
This commit is contained in:
@ -1088,12 +1088,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
|
||||
Path sort_path; /* dummy for result of cost_sort */
|
||||
Path agg_path; /* dummy for result of cost_agg */
|
||||
MemoryContext oldcontext;
|
||||
List *in_operators;
|
||||
List *uniq_exprs;
|
||||
bool all_btree;
|
||||
bool all_hash;
|
||||
int numCols;
|
||||
ListCell *lc;
|
||||
|
||||
/* Caller made a mistake if subpath isn't cheapest_total ... */
|
||||
Assert(subpath == rel->cheapest_total_path);
|
||||
@ -1106,8 +1101,8 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
|
||||
if (rel->cheapest_unique_path)
|
||||
return (UniquePath *) rel->cheapest_unique_path;
|
||||
|
||||
/* If we previously failed, return NULL quickly */
|
||||
if (sjinfo->join_quals == NIL)
|
||||
/* If it's not possible to unique-ify, return NULL */
|
||||
if (!(sjinfo->semi_can_btree || sjinfo->semi_can_hash))
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
@ -1116,150 +1111,6 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
|
||||
*/
|
||||
oldcontext = MemoryContextSwitchTo(root->planner_cxt);
|
||||
|
||||
/*----------
|
||||
* Look to see whether the semijoin's join quals consist of AND'ed
|
||||
* equality operators, with (only) RHS variables on only one side of
|
||||
* each one. If so, we can figure out how to enforce uniqueness for
|
||||
* the RHS.
|
||||
*
|
||||
* Note that the input join_quals list is the list of quals that are
|
||||
* *syntactically* associated with the semijoin, which in practice means
|
||||
* the synthesized comparison list for an IN or the WHERE of an EXISTS.
|
||||
* Particularly in the latter case, it might contain clauses that aren't
|
||||
* *semantically* associated with the join, but refer to just one side or
|
||||
* the other. We can ignore such clauses here, as they will just drop
|
||||
* down to be processed within one side or the other. (It is okay to
|
||||
* consider only the syntactically-associated clauses here because for a
|
||||
* semijoin, no higher-level quals could refer to the RHS, and so there
|
||||
* can be no other quals that are semantically associated with this join.
|
||||
* We do things this way because it is useful to be able to run this test
|
||||
* before we have extracted the list of quals that are actually
|
||||
* semantically associated with the particular join.)
|
||||
*
|
||||
* Note that the in_operators list consists of the joinqual operators
|
||||
* themselves (but commuted if needed to put the RHS value on the right).
|
||||
* These could be cross-type operators, in which case the operator
|
||||
* actually needed for uniqueness is a related single-type operator.
|
||||
* We assume here that that operator will be available from the btree
|
||||
* or hash opclass when the time comes ... if not, create_unique_plan()
|
||||
* will fail.
|
||||
*----------
|
||||
*/
|
||||
in_operators = NIL;
|
||||
uniq_exprs = NIL;
|
||||
all_btree = true;
|
||||
all_hash = enable_hashagg; /* don't consider hash if not enabled */
|
||||
foreach(lc, sjinfo->join_quals)
|
||||
{
|
||||
OpExpr *op = (OpExpr *) lfirst(lc);
|
||||
Oid opno;
|
||||
Node *left_expr;
|
||||
Node *right_expr;
|
||||
Relids left_varnos;
|
||||
Relids right_varnos;
|
||||
Relids all_varnos;
|
||||
Oid opinputtype;
|
||||
|
||||
/* Is it a binary opclause? */
|
||||
if (!IsA(op, OpExpr) ||
|
||||
list_length(op->args) != 2)
|
||||
{
|
||||
/* No, but does it reference both sides? */
|
||||
all_varnos = pull_varnos((Node *) op);
|
||||
if (!bms_overlap(all_varnos, sjinfo->syn_righthand) ||
|
||||
bms_is_subset(all_varnos, sjinfo->syn_righthand))
|
||||
{
|
||||
/*
|
||||
* Clause refers to only one rel, so ignore it --- unless it
|
||||
* contains volatile functions, in which case we'd better
|
||||
* punt.
|
||||
*/
|
||||
if (contain_volatile_functions((Node *) op))
|
||||
goto no_unique_path;
|
||||
continue;
|
||||
}
|
||||
/* Non-operator clause referencing both sides, must punt */
|
||||
goto no_unique_path;
|
||||
}
|
||||
|
||||
/* Extract data from binary opclause */
|
||||
opno = op->opno;
|
||||
left_expr = linitial(op->args);
|
||||
right_expr = lsecond(op->args);
|
||||
left_varnos = pull_varnos(left_expr);
|
||||
right_varnos = pull_varnos(right_expr);
|
||||
all_varnos = bms_union(left_varnos, right_varnos);
|
||||
opinputtype = exprType(left_expr);
|
||||
|
||||
/* Does it reference both sides? */
|
||||
if (!bms_overlap(all_varnos, sjinfo->syn_righthand) ||
|
||||
bms_is_subset(all_varnos, sjinfo->syn_righthand))
|
||||
{
|
||||
/*
|
||||
* Clause refers to only one rel, so ignore it --- unless it
|
||||
* contains volatile functions, in which case we'd better punt.
|
||||
*/
|
||||
if (contain_volatile_functions((Node *) op))
|
||||
goto no_unique_path;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* check rel membership of arguments */
|
||||
if (!bms_is_empty(right_varnos) &&
|
||||
bms_is_subset(right_varnos, sjinfo->syn_righthand) &&
|
||||
!bms_overlap(left_varnos, sjinfo->syn_righthand))
|
||||
{
|
||||
/* typical case, right_expr is RHS variable */
|
||||
}
|
||||
else if (!bms_is_empty(left_varnos) &&
|
||||
bms_is_subset(left_varnos, sjinfo->syn_righthand) &&
|
||||
!bms_overlap(right_varnos, sjinfo->syn_righthand))
|
||||
{
|
||||
/* flipped case, left_expr is RHS variable */
|
||||
opno = get_commutator(opno);
|
||||
if (!OidIsValid(opno))
|
||||
goto no_unique_path;
|
||||
right_expr = left_expr;
|
||||
}
|
||||
else
|
||||
goto no_unique_path;
|
||||
|
||||
/* all operators must be btree equality or hash equality */
|
||||
if (all_btree)
|
||||
{
|
||||
/* oprcanmerge is considered a hint... */
|
||||
if (!op_mergejoinable(opno, opinputtype) ||
|
||||
get_mergejoin_opfamilies(opno) == NIL)
|
||||
all_btree = false;
|
||||
}
|
||||
if (all_hash)
|
||||
{
|
||||
/* ... but oprcanhash had better be correct */
|
||||
if (!op_hashjoinable(opno, opinputtype))
|
||||
all_hash = false;
|
||||
}
|
||||
if (!(all_btree || all_hash))
|
||||
goto no_unique_path;
|
||||
|
||||
/* so far so good, keep building lists */
|
||||
in_operators = lappend_oid(in_operators, opno);
|
||||
uniq_exprs = lappend(uniq_exprs, copyObject(right_expr));
|
||||
}
|
||||
|
||||
/* Punt if we didn't find at least one column to unique-ify */
|
||||
if (uniq_exprs == NIL)
|
||||
goto no_unique_path;
|
||||
|
||||
/*
|
||||
* The expressions we'd need to unique-ify mustn't be volatile.
|
||||
*/
|
||||
if (contain_volatile_functions((Node *) uniq_exprs))
|
||||
goto no_unique_path;
|
||||
|
||||
/*
|
||||
* If we get here, we can unique-ify using at least one of sorting and
|
||||
* hashing. Start building the result Path object.
|
||||
*/
|
||||
pathnode = makeNode(UniquePath);
|
||||
|
||||
pathnode->path.pathtype = T_Unique;
|
||||
@ -1273,18 +1124,19 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
|
||||
pathnode->path.pathkeys = NIL;
|
||||
|
||||
pathnode->subpath = subpath;
|
||||
pathnode->in_operators = in_operators;
|
||||
pathnode->uniq_exprs = uniq_exprs;
|
||||
pathnode->in_operators = sjinfo->semi_operators;
|
||||
pathnode->uniq_exprs = sjinfo->semi_rhs_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
|
||||
* semi_rhs_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 &&
|
||||
if (rel->rtekind == RTE_RELATION && sjinfo->semi_can_btree &&
|
||||
relation_has_unique_index_for(root, rel, NIL,
|
||||
uniq_exprs, in_operators))
|
||||
sjinfo->semi_rhs_exprs,
|
||||
sjinfo->semi_operators))
|
||||
{
|
||||
pathnode->umethod = UNIQUE_PATH_NOOP;
|
||||
pathnode->path.rows = rel->rows;
|
||||
@ -1304,7 +1156,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
|
||||
* don't need to do anything. The test for uniqueness has to consider
|
||||
* exactly which columns we are extracting; for example "SELECT DISTINCT
|
||||
* x,y" doesn't guarantee that x alone is distinct. So we cannot check for
|
||||
* this optimization unless uniq_exprs consists only of simple Vars
|
||||
* this optimization unless semi_rhs_exprs consists only of simple Vars
|
||||
* referencing subquery outputs. (Possibly we could do something with
|
||||
* expressions in the subquery outputs, too, but for now keep it simple.)
|
||||
*/
|
||||
@ -1316,11 +1168,13 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
|
||||
{
|
||||
List *sub_tlist_colnos;
|
||||
|
||||
sub_tlist_colnos = translate_sub_tlist(uniq_exprs, rel->relid);
|
||||
sub_tlist_colnos = translate_sub_tlist(sjinfo->semi_rhs_exprs,
|
||||
rel->relid);
|
||||
|
||||
if (sub_tlist_colnos &&
|
||||
query_is_distinct_for(rte->subquery,
|
||||
sub_tlist_colnos, in_operators))
|
||||
sub_tlist_colnos,
|
||||
sjinfo->semi_operators))
|
||||
{
|
||||
pathnode->umethod = UNIQUE_PATH_NOOP;
|
||||
pathnode->path.rows = rel->rows;
|
||||
@ -1338,10 +1192,12 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
|
||||
}
|
||||
|
||||
/* Estimate number of output rows */
|
||||
pathnode->path.rows = estimate_num_groups(root, uniq_exprs, rel->rows);
|
||||
numCols = list_length(uniq_exprs);
|
||||
pathnode->path.rows = estimate_num_groups(root,
|
||||
sjinfo->semi_rhs_exprs,
|
||||
rel->rows);
|
||||
numCols = list_length(sjinfo->semi_rhs_exprs);
|
||||
|
||||
if (all_btree)
|
||||
if (sjinfo->semi_can_btree)
|
||||
{
|
||||
/*
|
||||
* Estimate cost for sort+unique implementation
|
||||
@ -1363,7 +1219,7 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
|
||||
sort_path.total_cost += cpu_operator_cost * rel->rows * numCols;
|
||||
}
|
||||
|
||||
if (all_hash)
|
||||
if (sjinfo->semi_can_hash)
|
||||
{
|
||||
/*
|
||||
* Estimate the overhead per hashtable entry at 64 bytes (same as in
|
||||
@ -1372,7 +1228,13 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
|
||||
int hashentrysize = rel->width + 64;
|
||||
|
||||
if (hashentrysize * pathnode->path.rows > work_mem * 1024L)
|
||||
all_hash = false; /* don't try to hash */
|
||||
{
|
||||
/*
|
||||
* We should not try to hash. Hack the SpecialJoinInfo to
|
||||
* remember this, in case we come through here again.
|
||||
*/
|
||||
sjinfo->semi_can_hash = false;
|
||||
}
|
||||
else
|
||||
cost_agg(&agg_path, root,
|
||||
AGG_HASHED, NULL,
|
||||
@ -1382,19 +1244,23 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
|
||||
rel->rows);
|
||||
}
|
||||
|
||||
if (all_btree && all_hash)
|
||||
if (sjinfo->semi_can_btree && sjinfo->semi_can_hash)
|
||||
{
|
||||
if (agg_path.total_cost < sort_path.total_cost)
|
||||
pathnode->umethod = UNIQUE_PATH_HASH;
|
||||
else
|
||||
pathnode->umethod = UNIQUE_PATH_SORT;
|
||||
}
|
||||
else if (all_btree)
|
||||
else if (sjinfo->semi_can_btree)
|
||||
pathnode->umethod = UNIQUE_PATH_SORT;
|
||||
else if (all_hash)
|
||||
else if (sjinfo->semi_can_hash)
|
||||
pathnode->umethod = UNIQUE_PATH_HASH;
|
||||
else
|
||||
goto no_unique_path;
|
||||
{
|
||||
/* we can get here only if we abandoned hashing above */
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (pathnode->umethod == UNIQUE_PATH_HASH)
|
||||
{
|
||||
@ -1412,15 +1278,6 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
|
||||
return pathnode;
|
||||
|
||||
no_unique_path: /* failure exit */
|
||||
|
||||
/* Mark the SpecialJoinInfo as not unique-able */
|
||||
sjinfo->join_quals = NIL;
|
||||
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
|
Reference in New Issue
Block a user