mirror of
https://git.postgresql.org/git/postgresql.git
synced 2025-03-07 19:47:50 +08:00
Clean up handling of constraint_exclusion and enable_partition_pruning.
The interaction of these parameters was a bit confused/confusing, and in fact v11 entirely misses the opportunity to apply partition constraints when a partition is accessed directly (rather than indirectly from its parent). In HEAD, establish the principle that enable_partition_pruning controls partition pruning and nothing else. When accessing a partition via its parent, we do partition pruning (if enabled by enable_partition_pruning) and then there is no need to consider partition constraints in the constraint_exclusion logic. When accessing a partition directly, its partition constraints are applied by the constraint_exclusion logic, only if constraint_exclusion = on. In v11, we can't have such a clean division of these GUCs' effects, partly because we don't want to break compatibility too much in a released branch, and partly because the clean coding requires inheritance_planner to have applied partition pruning to a partitioned target table, which it doesn't in v11. However, we can tweak things enough to cover the missed case, which seems like a good idea since it's potentially a performance regression from v10. This patch keeps v11's previous behavior in which enable_partition_pruning overrides constraint_exclusion for an inherited target table, though. In HEAD, also teach relation_excluded_by_constraints that it's okay to use inheritable constraints when trying to prune a traditional inheritance tree. This might not be thought worthy of effort given that that feature is semi-deprecated now, but we have enough infrastructure that it only takes a couple more lines of code to do it correctly. Amit Langote and Tom Lane Discussion: https://postgr.es/m/9813f079-f16b-61c8-9ab7-4363cab28d80@lab.ntt.co.jp Discussion: https://postgr.es/m/29069.1555970894@sss.pgh.pa.us
This commit is contained in:
parent
ad23adc5a1
commit
e03ff73969
@ -5084,10 +5084,11 @@ ANY <replaceable class="parameter">num_sync</replaceable> ( <replaceable class="
|
||||
The allowed values of <varname>constraint_exclusion</varname> are
|
||||
<literal>on</literal> (examine constraints for all tables),
|
||||
<literal>off</literal> (never examine constraints), and
|
||||
<literal>partition</literal> (examine constraints only for inheritance child
|
||||
tables and <literal>UNION ALL</literal> subqueries).
|
||||
<literal>partition</literal> (examine constraints only for inheritance
|
||||
child tables and <literal>UNION ALL</literal> subqueries).
|
||||
<literal>partition</literal> is the default setting.
|
||||
It is often used with inheritance tables to improve performance.
|
||||
It is often used with traditional inheritance trees to improve
|
||||
performance.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
@ -5111,15 +5112,19 @@ SELECT * FROM parent WHERE key = 2400;
|
||||
<para>
|
||||
Currently, constraint exclusion is enabled by default
|
||||
only for cases that are often used to implement table partitioning via
|
||||
inheritance tables. Turning it on for all tables imposes extra
|
||||
inheritance trees. Turning it on for all tables imposes extra
|
||||
planning overhead that is quite noticeable on simple queries, and most
|
||||
often will yield no benefit for simple queries. If you have no
|
||||
inheritance partitioned tables you might prefer to turn it off entirely.
|
||||
tables that are partitioned using traditional inheritance, you might
|
||||
prefer to turn it off entirely. (Note that the equivalent feature for
|
||||
partitioned tables is controlled by a separate parameter,
|
||||
<xref linkend="guc-enable-partition-pruning"/>.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Refer to <xref linkend="ddl-partitioning-constraint-exclusion"/> for
|
||||
more information on using constraint exclusion and partitioning.
|
||||
more information on using constraint exclusion to implement
|
||||
partitioning.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
@ -4534,25 +4534,12 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';
|
||||
</para>
|
||||
|
||||
<note>
|
||||
<para>
|
||||
Currently, pruning of partitions during the planning of an
|
||||
<command>UPDATE</command> or <command>DELETE</command> command is
|
||||
implemented using the constraint exclusion method (however, it is
|
||||
controlled by the <literal>enable_partition_pruning</literal> rather than
|
||||
<literal>constraint_exclusion</literal>) — see the following section
|
||||
for details and caveats that apply.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Execution-time partition pruning currently only occurs for the
|
||||
<literal>Append</literal> and <literal>MergeAppend</literal> node types.
|
||||
It is not yet implemented for the <literal>ModifyTable</literal> node
|
||||
type.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Both of these behaviors are likely to be changed in a future release
|
||||
of <productname>PostgreSQL</productname>.
|
||||
type, but that is likely to be changed in a future release of
|
||||
<productname>PostgreSQL</productname>.
|
||||
</para>
|
||||
</note>
|
||||
</sect2>
|
||||
|
@ -1513,8 +1513,9 @@ inheritance_planner(PlannerInfo *root)
|
||||
parent_rte->securityQuals = NIL;
|
||||
|
||||
/*
|
||||
* Mark whether we're planning a query to a partitioned table or an
|
||||
* inheritance parent.
|
||||
* HACK: setting this to a value other than INHKIND_NONE signals to
|
||||
* relation_excluded_by_constraints() to treat the result relation as
|
||||
* being an appendrel member.
|
||||
*/
|
||||
subroot->inhTargetKind =
|
||||
(rootRelation != 0) ? INHKIND_PARTITIONED : INHKIND_INHERITED;
|
||||
|
@ -67,7 +67,9 @@ static bool infer_collation_opclass_match(InferenceElem *elem, Relation idxRel,
|
||||
List *idxExprs);
|
||||
static List *get_relation_constraints(PlannerInfo *root,
|
||||
Oid relationObjectId, RelOptInfo *rel,
|
||||
bool include_notnull);
|
||||
bool include_noinherit,
|
||||
bool include_notnull,
|
||||
bool include_partition);
|
||||
static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
|
||||
Relation heapRelation);
|
||||
static List *get_relation_statistics(RelOptInfo *rel, Relation relation);
|
||||
@ -1134,16 +1136,22 @@ get_relation_data_width(Oid relid, int32 *attr_widths)
|
||||
/*
|
||||
* get_relation_constraints
|
||||
*
|
||||
* Retrieve the validated CHECK constraint expressions of the given relation.
|
||||
* Retrieve the applicable constraint expressions of the given relation.
|
||||
*
|
||||
* Returns a List (possibly empty) of constraint expressions. Each one
|
||||
* has been canonicalized, and its Vars are changed to have the varno
|
||||
* indicated by rel->relid. This allows the expressions to be easily
|
||||
* compared to expressions taken from WHERE.
|
||||
*
|
||||
* If include_noinherit is true, it's okay to include constraints that
|
||||
* are marked NO INHERIT.
|
||||
*
|
||||
* If include_notnull is true, "col IS NOT NULL" expressions are generated
|
||||
* and added to the result for each column that's marked attnotnull.
|
||||
*
|
||||
* If include_partition is true, and the relation is a partition,
|
||||
* also include the partitioning constraints.
|
||||
*
|
||||
* Note: at present this is invoked at most once per relation per planner
|
||||
* run, and in many cases it won't be invoked at all, so there seems no
|
||||
* point in caching the data in RelOptInfo.
|
||||
@ -1151,7 +1159,9 @@ get_relation_data_width(Oid relid, int32 *attr_widths)
|
||||
static List *
|
||||
get_relation_constraints(PlannerInfo *root,
|
||||
Oid relationObjectId, RelOptInfo *rel,
|
||||
bool include_notnull)
|
||||
bool include_noinherit,
|
||||
bool include_notnull,
|
||||
bool include_partition)
|
||||
{
|
||||
List *result = NIL;
|
||||
Index varno = rel->relid;
|
||||
@ -1175,10 +1185,13 @@ get_relation_constraints(PlannerInfo *root,
|
||||
|
||||
/*
|
||||
* If this constraint hasn't been fully validated yet, we must
|
||||
* ignore it here.
|
||||
* ignore it here. Also ignore if NO INHERIT and we weren't told
|
||||
* that that's safe.
|
||||
*/
|
||||
if (!constr->check[i].ccvalid)
|
||||
continue;
|
||||
if (constr->check[i].ccnoinherit && !include_noinherit)
|
||||
continue;
|
||||
|
||||
cexpr = stringToNode(constr->check[i].ccbin);
|
||||
|
||||
@ -1243,13 +1256,9 @@ get_relation_constraints(PlannerInfo *root,
|
||||
}
|
||||
|
||||
/*
|
||||
* Append partition predicates, if any.
|
||||
*
|
||||
* For selects, partition pruning uses the parent table's partition bound
|
||||
* descriptor, instead of constraint exclusion which is driven by the
|
||||
* individual partition's partition constraint.
|
||||
* Add partitioning constraints, if requested.
|
||||
*/
|
||||
if (enable_partition_pruning && root->parse->commandType != CMD_SELECT)
|
||||
if (include_partition && relation->rd_rel->relispartition)
|
||||
{
|
||||
List *pcqual = RelationGetPartitionQual(relation);
|
||||
|
||||
@ -1366,7 +1375,7 @@ get_relation_statistics(RelOptInfo *rel, Relation relation)
|
||||
*
|
||||
* Detect whether the relation need not be scanned because it has either
|
||||
* self-inconsistent restrictions, or restrictions inconsistent with the
|
||||
* relation's validated CHECK constraints.
|
||||
* relation's applicable constraints.
|
||||
*
|
||||
* Note: this examines only rel->relid, rel->reloptkind, and
|
||||
* rel->baserestrictinfo; therefore it can be called before filling in
|
||||
@ -1376,6 +1385,9 @@ bool
|
||||
relation_excluded_by_constraints(PlannerInfo *root,
|
||||
RelOptInfo *rel, RangeTblEntry *rte)
|
||||
{
|
||||
bool include_noinherit;
|
||||
bool include_notnull;
|
||||
bool include_partition = false;
|
||||
List *safe_restrictions;
|
||||
List *constraint_pred;
|
||||
List *safe_constraints;
|
||||
@ -1384,6 +1396,13 @@ relation_excluded_by_constraints(PlannerInfo *root,
|
||||
/* As of now, constraint exclusion works only with simple relations. */
|
||||
Assert(IS_SIMPLE_REL(rel));
|
||||
|
||||
/*
|
||||
* If there are no base restriction clauses, we have no hope of proving
|
||||
* anything below, so fall out quickly.
|
||||
*/
|
||||
if (rel->baserestrictinfo == NIL)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* Regardless of the setting of constraint_exclusion, detect
|
||||
* constant-FALSE-or-NULL restriction clauses. Because const-folding will
|
||||
@ -1410,35 +1429,41 @@ relation_excluded_by_constraints(PlannerInfo *root,
|
||||
switch (constraint_exclusion)
|
||||
{
|
||||
case CONSTRAINT_EXCLUSION_OFF:
|
||||
|
||||
/*
|
||||
* Don't prune if feature turned off -- except if the relation is
|
||||
* a partition. While partprune.c-style partition pruning is not
|
||||
* yet in use for all cases (update/delete is not handled), it
|
||||
* would be a UI horror to use different user-visible controls
|
||||
* depending on such a volatile implementation detail. Therefore,
|
||||
* for partitioned tables we use enable_partition_pruning to
|
||||
* control this behavior.
|
||||
*/
|
||||
if (root->inhTargetKind == INHKIND_PARTITIONED)
|
||||
break;
|
||||
/* In 'off' mode, never make any further tests */
|
||||
return false;
|
||||
|
||||
case CONSTRAINT_EXCLUSION_PARTITION:
|
||||
|
||||
/*
|
||||
* When constraint_exclusion is set to 'partition' we only handle
|
||||
* OTHER_MEMBER_RELs, or BASERELs in cases where the result target
|
||||
* is an inheritance parent or a partitioned table.
|
||||
* appendrel members. Normally, they are RELOPT_OTHER_MEMBER_REL
|
||||
* relations, but we also consider inherited target relations as
|
||||
* appendrel members for the purposes of constraint exclusion
|
||||
* (since, indeed, they were appendrel members earlier in
|
||||
* inheritance_planner).
|
||||
*
|
||||
* In both cases, partition pruning was already applied, so there
|
||||
* is no need to consider the rel's partition constraints here.
|
||||
*/
|
||||
if ((rel->reloptkind != RELOPT_OTHER_MEMBER_REL) &&
|
||||
!(rel->reloptkind == RELOPT_BASEREL &&
|
||||
root->inhTargetKind != INHKIND_NONE &&
|
||||
rel->relid == root->parse->resultRelation))
|
||||
return false;
|
||||
break;
|
||||
if (rel->reloptkind == RELOPT_OTHER_MEMBER_REL ||
|
||||
(rel->relid == root->parse->resultRelation &&
|
||||
root->inhTargetKind != INHKIND_NONE))
|
||||
break; /* appendrel member, so process it */
|
||||
return false;
|
||||
|
||||
case CONSTRAINT_EXCLUSION_ON:
|
||||
|
||||
/*
|
||||
* In 'on' mode, always apply constraint exclusion. If we are
|
||||
* considering a baserel that is a partition (i.e., it was
|
||||
* directly named rather than expanded from a parent table), then
|
||||
* its partition constraints haven't been considered yet, so
|
||||
* include them in the processing here.
|
||||
*/
|
||||
if (rel->reloptkind == RELOPT_BASEREL &&
|
||||
!(rel->relid == root->parse->resultRelation &&
|
||||
root->inhTargetKind != INHKIND_NONE))
|
||||
include_partition = true;
|
||||
break; /* always try to exclude */
|
||||
}
|
||||
|
||||
@ -1467,24 +1492,33 @@ relation_excluded_by_constraints(PlannerInfo *root,
|
||||
return true;
|
||||
|
||||
/*
|
||||
* Only plain relations have constraints. In a partitioning hierarchy,
|
||||
* but not with regular table inheritance, it's OK to assume that any
|
||||
* constraints that hold for the parent also hold for every child; for
|
||||
* instance, table inheritance allows the parent to have constraints
|
||||
* marked NO INHERIT, but table partitioning does not. We choose to check
|
||||
* whether the partitioning parents can be excluded here; doing so
|
||||
* consumes some cycles, but potentially saves us the work of excluding
|
||||
* each child individually.
|
||||
* Only plain relations have constraints, so stop here for other rtekinds.
|
||||
*/
|
||||
if (rte->rtekind != RTE_RELATION ||
|
||||
(rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE))
|
||||
if (rte->rtekind != RTE_RELATION)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* OK to fetch the constraint expressions. Include "col IS NOT NULL"
|
||||
* expressions for attnotnull columns, in case we can refute those.
|
||||
* If we are scanning just this table, we can use NO INHERIT constraints,
|
||||
* but not if we're scanning its children too. (Note that partitioned
|
||||
* tables should never have NO INHERIT constraints; but it's not necessary
|
||||
* for us to assume that here.)
|
||||
*/
|
||||
constraint_pred = get_relation_constraints(root, rte->relid, rel, true);
|
||||
include_noinherit = !rte->inh;
|
||||
|
||||
/*
|
||||
* Currently, attnotnull constraints must be treated as NO INHERIT unless
|
||||
* this is a partitioned table. In future we might track their
|
||||
* inheritance status more accurately, allowing this to be refined.
|
||||
*/
|
||||
include_notnull = (!rte->inh || rte->relkind == RELKIND_PARTITIONED_TABLE);
|
||||
|
||||
/*
|
||||
* Fetch the appropriate set of constraint expressions.
|
||||
*/
|
||||
constraint_pred = get_relation_constraints(root, rte->relid, rel,
|
||||
include_noinherit,
|
||||
include_notnull,
|
||||
include_partition);
|
||||
|
||||
/*
|
||||
* We do not currently enforce that CHECK constraints contain only
|
||||
|
@ -3639,4 +3639,46 @@ select * from listp where a = (select 2) and b <> 10;
|
||||
-> Result (never executed)
|
||||
(4 rows)
|
||||
|
||||
--
|
||||
-- check that a partition directly accessed in a query is excluded with
|
||||
-- constraint_exclusion = on
|
||||
--
|
||||
-- turn off partition pruning, so that it doesn't interfere
|
||||
set enable_partition_pruning to off;
|
||||
-- setting constraint_exclusion to 'partition' disables exclusion
|
||||
set constraint_exclusion to 'partition';
|
||||
explain (costs off) select * from listp1 where a = 2;
|
||||
QUERY PLAN
|
||||
--------------------
|
||||
Seq Scan on listp1
|
||||
Filter: (a = 2)
|
||||
(2 rows)
|
||||
|
||||
explain (costs off) update listp1 set a = 1 where a = 2;
|
||||
QUERY PLAN
|
||||
--------------------------
|
||||
Update on listp1
|
||||
-> Seq Scan on listp1
|
||||
Filter: (a = 2)
|
||||
(3 rows)
|
||||
|
||||
-- constraint exclusion enabled
|
||||
set constraint_exclusion to 'on';
|
||||
explain (costs off) select * from listp1 where a = 2;
|
||||
QUERY PLAN
|
||||
--------------------------
|
||||
Result
|
||||
One-Time Filter: false
|
||||
(2 rows)
|
||||
|
||||
explain (costs off) update listp1 set a = 1 where a = 2;
|
||||
QUERY PLAN
|
||||
--------------------------------
|
||||
Update on listp1
|
||||
-> Result
|
||||
One-Time Filter: false
|
||||
(3 rows)
|
||||
|
||||
reset constraint_exclusion;
|
||||
reset enable_partition_pruning;
|
||||
drop table listp;
|
||||
|
@ -990,4 +990,24 @@ create table listp2_10 partition of listp2 for values in (10);
|
||||
explain (analyze, costs off, summary off, timing off)
|
||||
select * from listp where a = (select 2) and b <> 10;
|
||||
|
||||
--
|
||||
-- check that a partition directly accessed in a query is excluded with
|
||||
-- constraint_exclusion = on
|
||||
--
|
||||
|
||||
-- turn off partition pruning, so that it doesn't interfere
|
||||
set enable_partition_pruning to off;
|
||||
|
||||
-- setting constraint_exclusion to 'partition' disables exclusion
|
||||
set constraint_exclusion to 'partition';
|
||||
explain (costs off) select * from listp1 where a = 2;
|
||||
explain (costs off) update listp1 set a = 1 where a = 2;
|
||||
-- constraint exclusion enabled
|
||||
set constraint_exclusion to 'on';
|
||||
explain (costs off) select * from listp1 where a = 2;
|
||||
explain (costs off) update listp1 set a = 1 where a = 2;
|
||||
|
||||
reset constraint_exclusion;
|
||||
reset enable_partition_pruning;
|
||||
|
||||
drop table listp;
|
||||
|
Loading…
Reference in New Issue
Block a user