From 3c3bb99330aa9b4c2f6258bfa0265d806bf365c3 Mon Sep 17 00:00:00 2001 From: Robert Haas Date: Thu, 2 Mar 2017 17:18:19 +0530 Subject: [PATCH] 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 --- doc/src/sgml/ddl.sgml | 47 ++++++++++++++--------------- doc/src/sgml/ref/analyze.sgml | 7 +++-- doc/src/sgml/ref/vacuum.sgml | 4 ++- src/backend/commands/analyze.c | 37 +++++++++++++++-------- src/backend/commands/tablecmds.c | 15 ++++++++-- src/backend/commands/vacuum.c | 51 ++++++++++++++++++++++++++++++-- 6 files changed, 116 insertions(+), 45 deletions(-) diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml index ef0f7cf727..09b5b3ff70 100644 --- a/doc/src/sgml/ddl.sgml +++ b/doc/src/sgml/ddl.sgml @@ -3792,8 +3792,7 @@ UNION ALL SELECT * FROM measurement_y2008m01; Caveats - The following caveats apply to partitioned tables implemented using either - method (unless noted otherwise): + The following caveats apply to using inheritance to implement partitioning: @@ -3803,13 +3802,6 @@ UNION ALL SELECT * FROM measurement_y2008m01; partitions and creates and/or modifies associated objects than to write each by hand. - - - 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. - @@ -3822,14 +3814,6 @@ UNION ALL SELECT * FROM measurement_y2008m01; on the partition tables, but it makes management of the structure much more complicated. - - - This problem exists even for partitioned tables. An 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. - @@ -3840,8 +3824,7 @@ UNION ALL SELECT * FROM measurement_y2008m01; ANALYZE measurement; - will only process the master table. This is true even for partitioned - tables. + will only process the master table. @@ -3852,11 +3835,27 @@ ANALYZE measurement; action is only taken in case of unique violations on the specified target relation, not its child relations. + + + + + The following caveats apply to partitioned tables created with the + explicit syntax: + + + + An 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. + + + + INSERT statements with ON CONFLICT - clause are currently not allowed on partitioned tables, that is, - cause error when specified. + clause are currently not allowed on partitioned tables. @@ -3864,7 +3863,8 @@ ANALYZE measurement; - The following caveats apply to constraint exclusion: + The following caveats apply to constraint exclusion, which is currently + used by both inheritance and partitioned tables: @@ -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. diff --git a/doc/src/sgml/ref/analyze.sgml b/doc/src/sgml/ref/analyze.sgml index 27ab4fca42..49727e22df 100644 --- a/doc/src/sgml/ref/analyze.sgml +++ b/doc/src/sgml/ref/analyze.sgml @@ -63,8 +63,11 @@ ANALYZE [ VERBOSE ] [ table_name [ 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. diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml index f18180a2fa..543ebcf649 100644 --- a/doc/src/sgml/ref/vacuum.sgml +++ b/doc/src/sgml/ref/vacuum.sgml @@ -153,7 +153,9 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ 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. diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index ed3acb1673..a70c760341 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -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", diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 3cea220421..317012068b 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -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; /* diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 812fb4a48f..3a9b965266 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -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 -- 2.40.0