Don't uselessly rewrite, truncate, VACUUM, or ANALYZE partitioned tables.

Also, recursively perform VACUUM and ANALYZE on partitions when the
command is applied to a partitioned table.  In passing, some related
documentation updates.

Amit Langote, reviewed by Michael Paquier, Ashutosh Bapat, and by me.

Discussion: http://postgr.es/m/47288cf1-f72c-dfc2-5ff0-4af962ae5c1b@lab.ntt.co.jp
This commit is contained in:
Robert Haas 2017-03-02 17:18:19 +05:30
parent fa42b2005f
commit 3c3bb99330
6 changed files with 116 additions and 45 deletions

View File

@ -3792,8 +3792,7 @@ UNION ALL SELECT * FROM measurement_y2008m01;
<title>Caveats</title> <title>Caveats</title>
<para> <para>
The following caveats apply to partitioned tables implemented using either The following caveats apply to using inheritance to implement partitioning:
method (unless noted otherwise):
<itemizedlist> <itemizedlist>
<listitem> <listitem>
<para> <para>
@ -3803,13 +3802,6 @@ UNION ALL SELECT * FROM measurement_y2008m01;
partitions and creates and/or modifies associated objects than partitions and creates and/or modifies associated objects than
to write each by hand. to write each by hand.
</para> </para>
<para>
This is not a problem with partitioned tables though, as trying to
create a partition that overlaps with one of the existing partitions
results in an error, so it is impossible to end up with partitions
that overlap one another.
</para>
</listitem> </listitem>
<listitem> <listitem>
@ -3822,14 +3814,6 @@ UNION ALL SELECT * FROM measurement_y2008m01;
on the partition tables, but it makes management of the structure on the partition tables, but it makes management of the structure
much more complicated. much more complicated.
</para> </para>
<para>
This problem exists even for partitioned tables. An <command>UPDATE</>
that causes a row to move from one partition to another fails, because
the new value of the row fails to satisfy the implicit partition
constraint of the original partition. This might change in future
releases.
</para>
</listitem> </listitem>
<listitem> <listitem>
@ -3840,8 +3824,7 @@ UNION ALL SELECT * FROM measurement_y2008m01;
<programlisting> <programlisting>
ANALYZE measurement; ANALYZE measurement;
</programlisting> </programlisting>
will only process the master table. This is true even for partitioned will only process the master table.
tables.
</para> </para>
</listitem> </listitem>
@ -3852,11 +3835,27 @@ ANALYZE measurement;
action is only taken in case of unique violations on the specified action is only taken in case of unique violations on the specified
target relation, not its child relations. target relation, not its child relations.
</para> </para>
</listitem>
</itemizedlist>
</para>
<para>
The following caveats apply to partitioned tables created with the
explicit syntax:
<itemizedlist>
<listitem>
<para>
An <command>UPDATE</> that causes a row to move from one partition to
another fails, because the new value of the row fails to satisfy the
implicit partition constraint of the original partition. This might
change in future releases.
</para>
</listitem>
<listitem>
<para> <para>
<command>INSERT</command> statements with <literal>ON CONFLICT</> <command>INSERT</command> statements with <literal>ON CONFLICT</>
clause are currently not allowed on partitioned tables, that is, clause are currently not allowed on partitioned tables.
cause error when specified.
</para> </para>
</listitem> </listitem>
@ -3864,7 +3863,8 @@ ANALYZE measurement;
</para> </para>
<para> <para>
The following caveats apply to constraint exclusion: The following caveats apply to constraint exclusion, which is currently
used by both inheritance and partitioned tables:
<itemizedlist> <itemizedlist>
<listitem> <listitem>
@ -3898,8 +3898,7 @@ ANALYZE measurement;
during constraint exclusion, so large numbers of partitions are likely during constraint exclusion, so large numbers of partitions are likely
to increase query planning time considerably. Partitioning using to increase query planning time considerably. Partitioning using
these techniques will work well with up to perhaps a hundred partitions; these techniques will work well with up to perhaps a hundred partitions;
don't try to use many thousands of partitions. This restriction applies don't try to use many thousands of partitions.
both to inheritance and explicit partitioning syntax.
</para> </para>
</listitem> </listitem>

View File

@ -63,8 +63,11 @@ ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
<listitem> <listitem>
<para> <para>
The name (possibly schema-qualified) of a specific table to The name (possibly schema-qualified) of a specific table to
analyze. If omitted, all regular tables (but not foreign tables) analyze. If omitted, all regular tables, partitioned tables, and
in the current database are analyzed. and materialized views in the current database are analyzed (but not
foreign tables). If the specified table is a partitioned table, both the
inheritance statistics of the partitioned table as a whole and
statistics of the individual partitions are updated.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>

View File

@ -153,7 +153,9 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem> <listitem>
<para> <para>
The name (optionally schema-qualified) of a specific table to The name (optionally schema-qualified) of a specific table to
vacuum. Defaults to all tables in the current database. vacuum. If omitted, all regular tables and materialized views in the
current database are vacuumed. If the specified table is a partitioned
table, all of its leaf partitions are vacuumed.
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>

View File

@ -201,8 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* locked the relation. * locked the relation.
*/ */
if (onerel->rd_rel->relkind == RELKIND_RELATION || if (onerel->rd_rel->relkind == RELKIND_RELATION ||
onerel->rd_rel->relkind == RELKIND_MATVIEW || onerel->rd_rel->relkind == RELKIND_MATVIEW)
onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{ {
/* Regular table, so we'll use the regular row acquisition function */ /* Regular table, so we'll use the regular row acquisition function */
acquirefunc = acquire_sample_rows; acquirefunc = acquire_sample_rows;
@ -234,6 +233,12 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
return; return;
} }
} }
else if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* For partitioned tables, we want to do the recursive ANALYZE below.
*/
}
else else
{ {
/* No need for a WARNING if we already complained during VACUUM */ /* No need for a WARNING if we already complained during VACUUM */
@ -253,10 +258,12 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
LWLockRelease(ProcArrayLock); LWLockRelease(ProcArrayLock);
/* /*
* Do the normal non-recursive ANALYZE. * Do the normal non-recursive ANALYZE. We can skip this for partitioned
* tables, which don't contain any rows.
*/ */
do_analyze_rel(onerel, options, params, va_cols, acquirefunc, relpages, if (onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
false, in_outer_xact, elevel); do_analyze_rel(onerel, options, params, va_cols, acquirefunc,
relpages, false, in_outer_xact, elevel);
/* /*
* If there are child tables, do recursive ANALYZE. * If there are child tables, do recursive ANALYZE.
@ -1260,6 +1267,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
nrels, nrels,
i; i;
ListCell *lc; ListCell *lc;
bool has_child;
/* /*
* Find all members of inheritance set. We only need AccessShareLock on * Find all members of inheritance set. We only need AccessShareLock on
@ -1297,6 +1305,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
relblocks = (double *) palloc(list_length(tableOIDs) * sizeof(double)); relblocks = (double *) palloc(list_length(tableOIDs) * sizeof(double));
totalblocks = 0; totalblocks = 0;
nrels = 0; nrels = 0;
has_child = false;
foreach(lc, tableOIDs) foreach(lc, tableOIDs)
{ {
Oid childOID = lfirst_oid(lc); Oid childOID = lfirst_oid(lc);
@ -1318,8 +1327,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
/* Check table type (MATVIEW can't happen, but might as well allow) */ /* Check table type (MATVIEW can't happen, but might as well allow) */
if (childrel->rd_rel->relkind == RELKIND_RELATION || if (childrel->rd_rel->relkind == RELKIND_RELATION ||
childrel->rd_rel->relkind == RELKIND_MATVIEW || childrel->rd_rel->relkind == RELKIND_MATVIEW)
childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{ {
/* Regular table, so use the regular row acquisition function */ /* Regular table, so use the regular row acquisition function */
acquirefunc = acquire_sample_rows; acquirefunc = acquire_sample_rows;
@ -1351,13 +1359,17 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
} }
else else
{ {
/* ignore, but release the lock on it */ /*
Assert(childrel != onerel); * ignore, but release the lock on it. could be a partitioned
heap_close(childrel, AccessShareLock); * table.
*/
if (childrel != onerel)
heap_close(childrel, AccessShareLock);
continue; continue;
} }
/* OK, we'll process this child */ /* OK, we'll process this child */
has_child = true;
rels[nrels] = childrel; rels[nrels] = childrel;
acquirefuncs[nrels] = acquirefunc; acquirefuncs[nrels] = acquirefunc;
relblocks[nrels] = (double) relpages; relblocks[nrels] = (double) relpages;
@ -1366,9 +1378,10 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
} }
/* /*
* If we don't have at least two tables to consider, fail. * If we don't have at least one child table to consider, fail. If the
* relation is a partitioned table, it's not counted as a child table.
*/ */
if (nrels < 2) if (!has_child)
{ {
ereport(elevel, ereport(elevel,
(errmsg("skipping analyze of \"%s.%s\" inheritance tree --- this inheritance tree contains no analyzable child tables", (errmsg("skipping analyze of \"%s.%s\" inheritance tree --- this inheritance tree contains no analyzable child tables",

View File

@ -1349,6 +1349,10 @@ ExecuteTruncate(TruncateStmt *stmt)
{ {
Relation rel = (Relation) lfirst(cell); Relation rel = (Relation) lfirst(cell);
/* Skip partitioned tables as there is nothing to do */
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
continue;
/* /*
* Normally, we need a transaction-safe truncation here. However, if * Normally, we need a transaction-safe truncation here. However, if
* the table was either created in the current (sub)transaction or has * the table was either created in the current (sub)transaction or has
@ -1459,7 +1463,11 @@ truncate_check_rel(Relation rel)
{ {
AclResult aclresult; AclResult aclresult;
/* Only allow truncate on regular tables */ /*
* Only allow truncate on regular tables and partitioned tables (although,
* the latter are only being included here for the following checks; no
* physical truncation will occur in their case.)
*/
if (rel->rd_rel->relkind != RELKIND_RELATION && if (rel->rd_rel->relkind != RELKIND_RELATION &&
rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE) rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR, ereport(ERROR,
@ -4006,8 +4014,9 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
{ {
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab); AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
/* Foreign tables have no storage. */ /* Foreign tables have no storage, nor do partitioned tables. */
if (tab->relkind == RELKIND_FOREIGN_TABLE) if (tab->relkind == RELKIND_FOREIGN_TABLE ||
tab->relkind == RELKIND_PARTITIONED_TABLE)
continue; continue;
/* /*

View File

@ -32,6 +32,7 @@
#include "access/xact.h" #include "access/xact.h"
#include "catalog/namespace.h" #include "catalog/namespace.h"
#include "catalog/pg_database.h" #include "catalog/pg_database.h"
#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_namespace.h" #include "catalog/pg_namespace.h"
#include "commands/cluster.h" #include "commands/cluster.h"
#include "commands/vacuum.h" #include "commands/vacuum.h"
@ -394,6 +395,9 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
{ {
/* Process a specific relation */ /* Process a specific relation */
Oid relid; Oid relid;
HeapTuple tuple;
Form_pg_class classForm;
bool include_parts;
/* /*
* Since we don't take a lock here, the relation might be gone, or the * Since we don't take a lock here, the relation might be gone, or the
@ -406,9 +410,29 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
*/ */
relid = RangeVarGetRelid(vacrel, NoLock, false); relid = RangeVarGetRelid(vacrel, NoLock, false);
/* Make a relation list entry for this guy */ /*
* To check whether the relation is a partitioned table, fetch its
* syscache entry.
*/
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", relid);
classForm = (Form_pg_class) GETSTRUCT(tuple);
include_parts = (classForm->relkind == RELKIND_PARTITIONED_TABLE);
ReleaseSysCache(tuple);
/*
* Make relation list entries for this guy and its partitions, if any.
* Note that the list returned by find_all_inheritors() include the
* passed-in OID at its head. Also note that we did not request a
* lock to be taken to match what would be done otherwise.
*/
oldcontext = MemoryContextSwitchTo(vac_context); oldcontext = MemoryContextSwitchTo(vac_context);
oid_list = lappend_oid(oid_list, relid); if (include_parts)
oid_list = list_concat(oid_list,
find_all_inheritors(relid, NoLock, NULL));
else
oid_list = lappend_oid(oid_list, relid);
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
} }
else else
@ -429,8 +453,14 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
{ {
Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple); Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple);
/*
* We include partitioned tables here; depending on which
* operation is to be performed, caller will decide whether to
* process or ignore them.
*/
if (classForm->relkind != RELKIND_RELATION && if (classForm->relkind != RELKIND_RELATION &&
classForm->relkind != RELKIND_MATVIEW) classForm->relkind != RELKIND_MATVIEW &&
classForm->relkind != RELKIND_PARTITIONED_TABLE)
continue; continue;
/* Make a relation list entry for this guy */ /* Make a relation list entry for this guy */
@ -1349,6 +1379,21 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
return false; return false;
} }
/*
* Ignore partitioned tables as there is no work to be done. Since we
* release the lock here, it's possible that any partitions added from
* this point on will not get processed, but that seems harmless.
*/
if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
relation_close(onerel, lmode);
PopActiveSnapshot();
CommitTransactionCommand();
/* It's OK for other commands to look at this table */
return true;
}
/* /*
* Get a session-level lock too. This will protect our access to the * Get a session-level lock too. This will protect our access to the
* relation across multiple transactions, so that we can vacuum the * relation across multiple transactions, so that we can vacuum the