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>
<para>
The following caveats apply to partitioned tables implemented using either
method (unless noted otherwise):
The following caveats apply to using inheritance to implement partitioning:
<itemizedlist>
<listitem>
<para>
@ -3803,13 +3802,6 @@ UNION ALL SELECT * FROM measurement_y2008m01;
partitions and creates and/or modifies associated objects than
to write each by hand.
</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>
@ -3822,14 +3814,6 @@ UNION ALL SELECT * FROM measurement_y2008m01;
on the partition tables, but it makes management of the structure
much more complicated.
</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>
@ -3840,8 +3824,7 @@ UNION ALL SELECT * FROM measurement_y2008m01;
<programlisting>
ANALYZE measurement;
</programlisting>
will only process the master table. This is true even for partitioned
tables.
will only process the master table.
</para>
</listitem>
@ -3852,11 +3835,27 @@ ANALYZE measurement;
action is only taken in case of unique violations on the specified
target relation, not its child relations.
</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>
<command>INSERT</command> statements with <literal>ON CONFLICT</>
clause are currently not allowed on partitioned tables, that is,
cause error when specified.
clause are currently not allowed on partitioned tables.
</para>
</listitem>
@ -3864,7 +3863,8 @@ ANALYZE measurement;
</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>
<listitem>
@ -3898,8 +3898,7 @@ ANALYZE measurement;
during constraint exclusion, so large numbers of partitions are likely
to increase query planning time considerably. Partitioning using
these techniques will work well with up to perhaps a hundred partitions;
don't try to use many thousands of partitions. This restriction applies
both to inheritance and explicit partitioning syntax.
don't try to use many thousands of partitions.
</para>
</listitem>

View File

@ -63,8 +63,11 @@ ANALYZE [ VERBOSE ] [ <replaceable class="PARAMETER">table_name</replaceable> [
<listitem>
<para>
The name (possibly schema-qualified) of a specific table to
analyze. If omitted, all regular tables (but not foreign tables)
in the current database are analyzed.
analyze. If omitted, all regular tables, partitioned tables, and
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>
</listitem>
</varlistentry>

View File

@ -153,7 +153,9 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">
<listitem>
<para>
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>
</listitem>
</varlistentry>

View File

@ -201,8 +201,7 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
* locked the relation.
*/
if (onerel->rd_rel->relkind == RELKIND_RELATION ||
onerel->rd_rel->relkind == RELKIND_MATVIEW ||
onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
onerel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so we'll use the regular row acquisition function */
acquirefunc = acquire_sample_rows;
@ -234,6 +233,12 @@ analyze_rel(Oid relid, RangeVar *relation, int options,
return;
}
}
else if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
/*
* For partitioned tables, we want to do the recursive ANALYZE below.
*/
}
else
{
/* 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);
/*
* 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,
false, in_outer_xact, elevel);
if (onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
do_analyze_rel(onerel, options, params, va_cols, acquirefunc,
relpages, false, in_outer_xact, elevel);
/*
* If there are child tables, do recursive ANALYZE.
@ -1260,6 +1267,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
nrels,
i;
ListCell *lc;
bool has_child;
/*
* 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));
totalblocks = 0;
nrels = 0;
has_child = false;
foreach(lc, tableOIDs)
{
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) */
if (childrel->rd_rel->relkind == RELKIND_RELATION ||
childrel->rd_rel->relkind == RELKIND_MATVIEW ||
childrel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
childrel->rd_rel->relkind == RELKIND_MATVIEW)
{
/* Regular table, so use the regular row acquisition function */
acquirefunc = acquire_sample_rows;
@ -1351,13 +1359,17 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
}
else
{
/* ignore, but release the lock on it */
Assert(childrel != onerel);
heap_close(childrel, AccessShareLock);
/*
* ignore, but release the lock on it. could be a partitioned
* table.
*/
if (childrel != onerel)
heap_close(childrel, AccessShareLock);
continue;
}
/* OK, we'll process this child */
has_child = true;
rels[nrels] = childrel;
acquirefuncs[nrels] = acquirefunc;
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,
(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);
/* 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
* the table was either created in the current (sub)transaction or has
@ -1459,7 +1463,11 @@ truncate_check_rel(Relation rel)
{
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 &&
rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
ereport(ERROR,
@ -4006,8 +4014,9 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
{
AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab);
/* Foreign tables have no storage. */
if (tab->relkind == RELKIND_FOREIGN_TABLE)
/* Foreign tables have no storage, nor do partitioned tables. */
if (tab->relkind == RELKIND_FOREIGN_TABLE ||
tab->relkind == RELKIND_PARTITIONED_TABLE)
continue;
/*

View File

@ -32,6 +32,7 @@
#include "access/xact.h"
#include "catalog/namespace.h"
#include "catalog/pg_database.h"
#include "catalog/pg_inherits_fn.h"
#include "catalog/pg_namespace.h"
#include "commands/cluster.h"
#include "commands/vacuum.h"
@ -394,6 +395,9 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
{
/* Process a specific relation */
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
@ -406,9 +410,29 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
*/
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);
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);
}
else
@ -429,8 +453,14 @@ get_rel_oids(Oid relid, const RangeVar *vacrel)
{
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 &&
classForm->relkind != RELKIND_MATVIEW)
classForm->relkind != RELKIND_MATVIEW &&
classForm->relkind != RELKIND_PARTITIONED_TABLE)
continue;
/* Make a relation list entry for this guy */
@ -1349,6 +1379,21 @@ vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
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
* relation across multiple transactions, so that we can vacuum the