Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 497bac7

Browse files
committed
Fix long-obsolete code for separating filter conditions in cost_index().
This code relied on pointer equality to identify which restriction clauses also appear in the indexquals (and, therefore, don't need to be applied as simple filter conditions). That was okay once upon a time, years ago, before we introduced the equivalence-class machinery. Now there's about a 50-50 chance that an equality clause appearing in the indexquals will be the mirror image (commutator) of its mate in the restriction list. When that happens, we'd erroneously think that the clause would be re-evaluated at each visited row, and therefore inflate the cost estimate for the indexscan by the clause's cost. Add some logic to catch this case. It seems to me that it continues not to be worthwhile to expend the extra predicate-proof work that createplan.c will do on the finally-selected plan, but this case is common enough and cheap enough to handle that we should do so. This will make a small difference (about one cpu_operator_cost per row) in simple cases; but in situations where there's an expensive function in the indexquals, it can make a very large difference, as seen in recent example from Jeff Janes. This is a long-standing bug, but I'm hesitant to back-patch because of the possibility of destabilizing plan choices that people may be happy with.
1 parent 5223dda commit 497bac7

File tree

2 files changed

+61
-20
lines changed

2 files changed

+61
-20
lines changed

src/backend/optimizer/path/costsize.c

+58-20
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ typedef struct
124124
QualCost total;
125125
} cost_qual_eval_context;
126126

127+
static List *extract_nonindex_conditions(List *qual_clauses, List *indexquals);
127128
static MergeScanSelCache *cached_scansel(PlannerInfo *root,
128129
RestrictInfo *rinfo,
129130
PathKey *pathkey);
@@ -242,7 +243,7 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count)
242243
IndexOptInfo *index = path->indexinfo;
243244
RelOptInfo *baserel = index->rel;
244245
bool indexonly = (path->path.pathtype == T_IndexOnlyScan);
245-
List *allclauses;
246+
List *qpquals;
246247
Cost startup_cost = 0;
247248
Cost run_cost = 0;
248249
Cost indexStartupCost;
@@ -265,19 +266,26 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count)
265266
Assert(baserel->relid > 0);
266267
Assert(baserel->rtekind == RTE_RELATION);
267268

268-
/* Mark the path with the correct row estimate */
269+
/*
270+
* Mark the path with the correct row estimate, and identify which quals
271+
* will need to be enforced as qpquals.
272+
*/
269273
if (path->path.param_info)
270274
{
271275
path->path.rows = path->path.param_info->ppi_rows;
272-
/* also get the set of clauses that should be enforced by the scan */
273-
allclauses = list_concat(list_copy(path->path.param_info->ppi_clauses),
274-
baserel->baserestrictinfo);
276+
/* qpquals come from the rel's restriction clauses and ppi_clauses */
277+
qpquals = list_concat(
278+
extract_nonindex_conditions(baserel->baserestrictinfo,
279+
path->indexquals),
280+
extract_nonindex_conditions(path->path.param_info->ppi_clauses,
281+
path->indexquals));
275282
}
276283
else
277284
{
278285
path->path.rows = baserel->rows;
279-
/* allclauses should just be the rel's restriction clauses */
280-
allclauses = baserel->baserestrictinfo;
286+
/* qpquals come from just the rel's restriction clauses */
287+
qpquals = extract_nonindex_conditions(baserel->baserestrictinfo,
288+
path->indexquals);
281289
}
282290

283291
if (!enable_indexscan)
@@ -433,19 +441,9 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count)
433441
* Estimate CPU costs per tuple.
434442
*
435443
* What we want here is cpu_tuple_cost plus the evaluation costs of any
436-
* qual clauses that we have to evaluate as qpquals. We approximate that
437-
* list as allclauses minus any clauses appearing in indexquals. (We
438-
* assume that pointer equality is enough to recognize duplicate
439-
* RestrictInfos.) This method neglects some considerations such as
440-
* clauses that needn't be checked because they are implied by a partial
441-
* index's predicate. It does not seem worth the cycles to try to factor
442-
* those things in at this stage, even though createplan.c will take pains
443-
* to remove such unnecessary clauses from the qpquals list if this path
444-
* is selected for use.
445-
*/
446-
cost_qual_eval(&qpqual_cost,
447-
list_difference_ptr(allclauses, path->indexquals),
448-
root);
444+
* qual clauses that we have to evaluate as qpquals.
445+
*/
446+
cost_qual_eval(&qpqual_cost, qpquals, root);
449447

450448
startup_cost += qpqual_cost.startup;
451449
cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;
@@ -456,6 +454,46 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count)
456454
path->path.total_cost = startup_cost + run_cost;
457455
}
458456

457+
/*
458+
* extract_nonindex_conditions
459+
*
460+
* Given a list of quals to be enforced in an indexscan, extract the ones that
461+
* will have to be applied as qpquals (ie, the index machinery won't handle
462+
* them). The actual rules for this appear in create_indexscan_plan() in
463+
* createplan.c, but the full rules are fairly expensive and we don't want to
464+
* go to that much effort for index paths that don't get selected for the
465+
* final plan. So we approximate it as quals that don't appear directly in
466+
* indexquals and also are not redundant children of the same EquivalenceClass
467+
* as some indexqual. This method neglects some infrequently-relevant
468+
* considerations such as clauses that needn't be checked because they are
469+
* implied by a partial index's predicate. It does not seem worth the cycles
470+
* to try to factor those things in at this stage, even though createplan.c
471+
* will take pains to remove such unnecessary clauses from the qpquals list if
472+
* this path is selected for use.
473+
*/
474+
static List *
475+
extract_nonindex_conditions(List *qual_clauses, List *indexquals)
476+
{
477+
List *result = NIL;
478+
ListCell *lc;
479+
480+
foreach(lc, qual_clauses)
481+
{
482+
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
483+
484+
Assert(IsA(rinfo, RestrictInfo));
485+
if (rinfo->pseudoconstant)
486+
continue; /* we may drop pseudoconstants here */
487+
if (list_member_ptr(indexquals, rinfo))
488+
continue; /* simple duplicate */
489+
if (is_redundant_derived_clause(rinfo, indexquals))
490+
continue; /* derived from same EquivalenceClass */
491+
/* ... skip the predicate proof attempts createplan.c will try ... */
492+
result = lappend(result, rinfo);
493+
}
494+
return result;
495+
}
496+
459497
/*
460498
* index_pages_fetched
461499
* Estimate the number of pages actually fetched after accounting for

src/backend/optimizer/plan/createplan.c

+3
Original file line numberDiff line numberDiff line change
@@ -1210,6 +1210,9 @@ create_indexscan_plan(PlannerInfo *root,
12101210
* predicate, but only in a plain SELECT; when scanning a target relation
12111211
* of UPDATE/DELETE/SELECT FOR UPDATE, we must leave such quals in the
12121212
* plan so that they'll be properly rechecked by EvalPlanQual testing.
1213+
*
1214+
* Note: if you change this bit of code you should also look at
1215+
* extract_nonindex_conditions() in costsize.c.
12131216
*/
12141217
qpqual = NIL;
12151218
foreach(l, scan_clauses)

0 commit comments

Comments
 (0)