]> granicus.if.org Git - postgresql/commitdiff
Clean up handling of constraint_exclusion and enable_partition_pruning.
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 30 Apr 2019 19:03:35 +0000 (15:03 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Tue, 30 Apr 2019 19:03:35 +0000 (15:03 -0400)
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

doc/src/sgml/config.sgml
doc/src/sgml/ddl.sgml
src/backend/optimizer/plan/planner.c
src/backend/optimizer/util/plancat.c
src/test/regress/expected/partition_prune.out
src/test/regress/sql/partition_prune.sql

index d94d0333aadb5225279f3560721ab5b72db81772..37e21e8dcb99bf9cfed5cfc7e67ccff11ed048c9 100644 (file)
@@ -4410,10 +4410,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>
@@ -4437,15 +4438,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>
index 59685d741620737bc1a2e94e837935003b3375f4..f53e3c6bec1071d46065dbcd9ee7f631cde263eb 100644 (file)
@@ -3918,22 +3918,11 @@ EXPLAIN SELECT count(*) FROM measurement WHERE logdate &gt;= DATE '2008-01-01';
 
    <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>) &mdash; see the following section
-     for details and caveats that apply.
-    </para>
-
-    <para>
-     Also, execution-time partition pruning currently only occurs for the
-     <literal>Append</literal> node type, not <literal>MergeAppend</literal>.
-    </para>
-
-    <para>
-     Both of these behaviors are likely to be changed in a future release
-     of <productname>PostgreSQL</productname>.
+     Execution-time partition pruning currently only occurs for the
+     <literal>Append</literal> node type, not
+     for <literal>MergeAppend</literal> or <literal>ModifyTable</literal>
+     nodes.  That is likely to be changed in a future release of
+     <productname>PostgreSQL</productname>.
     </para>
    </note>
   </sect2>
index 94b962bb6eec9075224a49c350de494d386a26f2..0f46914e540731be62fde7f551e99736b46e0a95 100644 (file)
@@ -1324,8 +1324,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 =
                        partitioned_relids ? INHKIND_PARTITIONED : INHKIND_INHERITED;
index 8369e3ad62dde0bd012964ae3894e6e9a1956575..223131a9e5c37bcb819aa7f632a103f397104ecf 100644 (file)
@@ -66,7 +66,9 @@ static bool infer_collation_opclass_match(InferenceElem *elem, Relation idxRel,
 static int32 get_rel_data_width(Relation rel, 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);
 static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
                                  Relation heapRelation);
 static List *get_relation_statistics(RelOptInfo *rel, Relation relation);
@@ -1157,16 +1159,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.
@@ -1174,7 +1182,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;
@@ -1198,10 +1208,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);
 
@@ -1266,13 +1279,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);
 
@@ -1377,7 +1386,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
@@ -1387,6 +1396,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;
@@ -1395,6 +1407,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
@@ -1415,6 +1434,17 @@ relation_excluded_by_constraints(PlannerInfo *root,
                        return true;
        }
 
+       /*
+        * Partition pruning will not have been applied to an inherited target
+        * relation, so if enable_partition_pruning is true, force consideration
+        * of the rel's partition constraints.  (Thus constraint_exclusion will be
+        * effectively forced 'on' for this case.  This is done better in v12.)
+        */
+       if (enable_partition_pruning &&
+               rel->relid == root->parse->resultRelation &&
+               root->inhTargetKind != INHKIND_NONE)
+               include_partition = true;
+
        /*
         * Skip further tests, depending on constraint_exclusion.
         */
@@ -1423,15 +1453,10 @@ relation_excluded_by_constraints(PlannerInfo *root,
                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.
+                        * In 'off' mode, never make any further tests, except if forcing
+                        * include_partition.
                         */
-                       if (root->inhTargetKind == INHKIND_PARTITIONED)
+                       if (include_partition)
                                break;
                        return false;
 
@@ -1439,17 +1464,32 @@ relation_excluded_by_constraints(PlannerInfo *root,
 
                        /*
                         * 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.
+                        *
+                        * In the former case, partition pruning was already applied, so
+                        * there is no need to consider the rel's partition constraints
+                        * here.  In the latter case, we already set include_partition
+                        * properly (i.e., do it if enable_partition_pruning).
                         */
-                       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)
+                               include_partition = true;
                        break;                          /* always try to exclude */
        }
 
@@ -1478,24 +1518,40 @@ 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)
+               return false;
+
+       /*
+        * 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.
+        *
+        * This is unnecessarily stupid, but making it smarter seems out of scope
+        * for v11.
         */
-       if (rte->rtekind != RTE_RELATION ||
-               (rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE))
+       if (rte->inh && rte->relkind != RELKIND_PARTITIONED_TABLE)
                return false;
 
        /*
-        * OK to fetch the constraint expressions.  Include "col IS NOT NULL"
-        * expressions for attnotnull columns, in case we can refute those.
+        * Given the above restriction, we can always include NO INHERIT and NOT
+        * NULL constraints.
+        */
+       include_noinherit = true;
+       include_notnull = true;
+
+       /*
+        * Fetch the appropriate set of constraint expressions.
         */
-       constraint_pred = get_relation_constraints(root, rte->relid, rel, true);
+       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
index 79e29e762b6c9ac8ec86db34bf1b65efd4d9a5d9..6f320dfff4465dbfe80bf9e5e60c45d3f519810e 100644 (file)
@@ -3417,4 +3417,46 @@ select * from listp where a = (select 2) and b <> 10;
          Filter: ((b <> 10) AND (a = $0))
 (5 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;
index 6aecf25f467a9ba3674161866dad03d300160334..eafbec6f3577c94efc6ef693a250df4c11d728e8 100644 (file)
@@ -899,4 +899,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;