diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml index c582021d29..3ca300b859 100644 --- a/doc/src/sgml/ref/vacuum.sgml +++ b/doc/src/sgml/ref/vacuum.sgml @@ -366,7 +366,13 @@ VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ ANALYZE ] [ rels, ¶ms, NULL, isTopLevel); } @@ -297,6 +346,7 @@ vacuum(List *relations, VacuumParams *params, BufferAccessStrategy bstrategy, bool isTopLevel) { static bool in_vacuum = false; + bool single_table_ok = false; const char *stmttype; volatile bool in_outer_xact, @@ -306,15 +356,22 @@ vacuum(List *relations, VacuumParams *params, stmttype = (params->options & VACOPT_VACUUM) ? "VACUUM" : "ANALYZE"; + if ((params->options & VACOPT_FULL) != 0 && + relations != NIL && + list_length(relations) == 1) + single_table_ok = true; + /* - * We cannot run VACUUM inside a user transaction block; if we were inside - * a transaction, then our commit- and start-transaction-command calls + * Single-table VACUUM can run inside a user transaction block, but + * we cannot run multiple VACUUMs inside a user transaction block; if we were + * inside a transaction, then our commit- and start-transaction-command calls * would not have the intended effect! There are numerous other subtle - * dependencies on this, too. + * dependencies on this, too, so when running in a transaction block, vacuum + * will skip some of its normal actions, see later for details. * * ANALYZE (without VACUUM) can run either way. */ - if (params->options & VACOPT_VACUUM) + if (params->options & VACOPT_VACUUM && !single_table_ok) { PreventInTransactionBlock(isTopLevel, stmttype); in_outer_xact = false; @@ -401,9 +458,8 @@ vacuum(List *relations, VacuumParams *params, * Decide whether we need to start/commit our own transactions. * * For VACUUM (with or without ANALYZE): always do so, so that we can - * release locks as soon as possible. (We could possibly use the outer - * transaction for a one-table VACUUM, but handling TOAST tables would be - * problematic.) + * release locks as soon as possible, except for a single table VACUUM + * when it is executed inside a transaction block. * * For ANALYZE (no VACUUM): if inside a transaction block, we cannot * start/commit our own transactions. Also, there's no need to do so if @@ -412,7 +468,7 @@ vacuum(List *relations, VacuumParams *params, * transactions so we can release locks sooner. */ if (params->options & VACOPT_VACUUM) - use_own_xacts = true; + use_own_xacts = !in_outer_xact; else { Assert(params->options & VACOPT_ANALYZE); @@ -427,6 +483,7 @@ vacuum(List *relations, VacuumParams *params, } /* + * Tell vacuum_rel whether it will need to manage its own transaction. If so, * vacuum_rel expects to be entered with no transaction active; it will * start and commit its own transaction. But we are called by an SQL * command, and so we are executing inside a transaction already. We @@ -437,6 +494,9 @@ vacuum(List *relations, VacuumParams *params, if (use_own_xacts) { Assert(!in_outer_xact); + Assert(!IsInTransactionBlock(isTopLevel)); + + params->use_own_xact = true; /* ActiveSnapshot is not set by autovacuum */ if (ActiveSnapshotSet()) @@ -445,6 +505,8 @@ vacuum(List *relations, VacuumParams *params, /* matches the StartTransaction in PostgresMain() */ CommitTransactionCommand(); } + else + params->use_own_xact = false; /* Turn vacuum cost accounting on or off, and set/clear in_vacuum */ PG_TRY(); @@ -1837,9 +1899,10 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) Assert(params != NULL); /* Begin a transaction for vacuuming this relation */ - StartTransactionCommand(); + if (params->use_own_xact) + StartTransactionCommand(); - if (!(params->options & VACOPT_FULL)) + if (!(params->options & VACOPT_FULL) && (params->use_own_xact)) { /* * In lazy vacuum, we can set the PROC_IN_VACUUM flag, which lets @@ -1852,6 +1915,11 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) * contents of other tables is arguably broken, but we won't break it * here by violating transaction semantics.) * + * Don't set PROC_IN_VACUUM if running in a transaction block, since it + * would be very bad for other users to ignore our xact in that case. + * Note that setting the flag is an optional performance tweak, not + * required for correct operation of VACUUM. + * * We also set the VACUUM_FOR_WRAPAROUND flag, which is passed down by * autovacuum; it's used to avoid canceling a vacuum that was invoked * in an emergency. @@ -1876,7 +1944,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) * cutoff xids in local memory wrapping around, and to have updated xmin * horizons. */ - PushActiveSnapshot(GetTransactionSnapshot()); + if (params->use_own_xact) + PushActiveSnapshot(GetTransactionSnapshot()); /* * Check for user-requested abort. Note we want this to be inside a @@ -1899,8 +1968,11 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) /* leave if relation could not be opened or locked */ if (!rel) { - PopActiveSnapshot(); - CommitTransactionCommand(); + if (params->use_own_xact) + { + PopActiveSnapshot(); + CommitTransactionCommand(); + } return false; } @@ -1917,8 +1989,11 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) params->options & VACOPT_VACUUM)) { relation_close(rel, lmode); - PopActiveSnapshot(); - CommitTransactionCommand(); + if (params->use_own_xact) + { + PopActiveSnapshot(); + CommitTransactionCommand(); + } return false; } @@ -1934,8 +2009,11 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) (errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables", RelationGetRelationName(rel)))); relation_close(rel, lmode); - PopActiveSnapshot(); - CommitTransactionCommand(); + if (params->use_own_xact) + { + PopActiveSnapshot(); + CommitTransactionCommand(); + } return false; } @@ -1949,8 +2027,11 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) if (RELATION_IS_OTHER_TEMP(rel)) { relation_close(rel, lmode); - PopActiveSnapshot(); - CommitTransactionCommand(); + if (params->use_own_xact) + { + PopActiveSnapshot(); + CommitTransactionCommand(); + } return false; } @@ -1962,8 +2043,11 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) { relation_close(rel, lmode); - PopActiveSnapshot(); - CommitTransactionCommand(); + if (params->use_own_xact) + { + PopActiveSnapshot(); + CommitTransactionCommand(); + } /* It's OK to proceed with ANALYZE on this table */ return true; } @@ -1977,9 +2061,13 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) * NOTE: this cannot block, even if someone else is waiting for access, * because the lock manager knows that both lock requests are from the * same process. + * + * If we are in a transaction block, vacuum both main table and toast + * table within the existing transaction, so no session lock required. */ lockrelid = rel->rd_lockInfo.lockRelId; - LockRelationIdForSession(&lockrelid, lmode); + if (params->use_own_xact) + LockRelationIdForSession(&lockrelid, lmode); /* * Set index_cleanup option based on index_cleanup reloption if it wasn't @@ -2075,8 +2163,11 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) /* * Complete the transaction and free all temporary memory used. */ - PopActiveSnapshot(); - CommitTransactionCommand(); + if (params->use_own_xact) + { + PopActiveSnapshot(); + CommitTransactionCommand(); + } /* * If the relation has a secondary toast rel, vacuum that too while we @@ -2091,7 +2182,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) /* * Now release the session-level lock on the main table. */ - UnlockRelationIdForSession(&lockrelid, lmode); + if (params->use_own_xact) + UnlockRelationIdForSession(&lockrelid, lmode); /* Report that we really did it. */ return true; diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 601834d4b4..c6a15097b5 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -258,6 +258,7 @@ typedef struct AutoVacuumWorkItem Oid avw_database; Oid avw_relation; BlockNumber avw_blockNumber; + bits32 avw_vac_options; } AutoVacuumWorkItem; #define NUM_WORKITEMS 256 @@ -2663,6 +2664,41 @@ perform_work_item(AutoVacuumWorkItem *workitem) ObjectIdGetDatum(workitem->avw_relation), Int64GetDatum((int64) workitem->avw_blockNumber)); break; + case AVW_VacuumImmediate: + { + BufferAccessStrategy bstrategy; + autovac_table tab; + + tab.at_relid = workitem->avw_relation; + + /* Set options */ + tab.at_params.options = workitem->avw_vac_options; + tab.at_params.index_cleanup = VACOPTVALUE_ENABLED; + tab.at_params.truncate = VACOPTVALUE_DISABLED; + + /* Deliberately avoid using autovacuum parameters, since this is immediate */ + tab.at_vacuum_cost_delay = VacuumCostDelay; + tab.at_vacuum_cost_limit = VacuumCostLimit; + tab.at_dobalance = false; + tab.at_sharedrel = false; + + /* Set names in case of error */ + tab.at_relname = pstrdup(get_rel_name(tab.at_relid)); + tab.at_nspname = pstrdup(get_namespace_name(get_rel_namespace(tab.at_relid))); + tab.at_datname = pstrdup(get_database_name(MyDatabaseId)); + + /* XXX what other options should we set? */ + + /* + * Create a buffer access strategy object for VACUUM to use. We want to + * use the same one across all the vacuum operations we perform, since the + * point is for VACUUM not to blow out the shared cache. + */ + bstrategy = GetAccessStrategy(BAS_VACUUM); + + autovacuum_do_vac_analyze(&tab, bstrategy); + } + break; default: elog(WARNING, "unrecognized work item found: type %d", workitem->avw_type); @@ -3210,6 +3246,11 @@ autovac_report_workitem(AutoVacuumWorkItem *workitem, snprintf(activity, MAX_AUTOVAC_ACTIV_LEN, "autovacuum: BRIN summarize"); break; + + case AVW_VacuumImmediate: + snprintf(activity, MAX_AUTOVAC_ACTIV_LEN, + "autovacuum: user vacuum"); + break; } /* @@ -3250,7 +3291,7 @@ AutoVacuumingActive(void) */ bool AutoVacuumRequestWork(AutoVacuumWorkItemType type, Oid relationId, - BlockNumber blkno) + BlockNumber blkno, bits32 options) { int i; bool result = false; @@ -3273,6 +3314,7 @@ AutoVacuumRequestWork(AutoVacuumWorkItemType type, Oid relationId, workitem->avw_database = MyDatabaseId; workitem->avw_relation = relationId; workitem->avw_blockNumber = blkno; + workitem->avw_vac_options = options; result = true; /* done */ @@ -3281,6 +3323,8 @@ AutoVacuumRequestWork(AutoVacuumWorkItemType type, Oid relationId, LWLockRelease(AutovacuumLock); + /* XXX Should this wake up the AVL? */ + return result; } diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h index 5d816ba7f4..35310c588b 100644 --- a/src/include/commands/vacuum.h +++ b/src/include/commands/vacuum.h @@ -227,6 +227,8 @@ typedef struct VacuumParams VacOptValue index_cleanup; /* Do index vacuum and cleanup */ VacOptValue truncate; /* Truncate empty pages at the end */ + bool use_own_xact; /* Use own xact in vacuum_rel? */ + /* * The number of parallel vacuum workers. 0 by default which means choose * based on the number of indexes. -1 indicates parallel vacuum is diff --git a/src/include/postmaster/autovacuum.h b/src/include/postmaster/autovacuum.h index 9d40fd6d54..f8b91f111b 100644 --- a/src/include/postmaster/autovacuum.h +++ b/src/include/postmaster/autovacuum.h @@ -22,7 +22,8 @@ */ typedef enum { - AVW_BRINSummarizeRange + AVW_BRINSummarizeRange, + AVW_VacuumImmediate } AutoVacuumWorkItemType; @@ -74,7 +75,7 @@ extern void AutovacuumLauncherIAm(void); #endif extern bool AutoVacuumRequestWork(AutoVacuumWorkItemType type, - Oid relationId, BlockNumber blkno); + Oid relationId, BlockNumber blkno, bits32 options); /* shared memory stuff */ extern Size AutoVacuumShmemSize(void); diff --git a/src/test/regress/expected/vacuum.out b/src/test/regress/expected/vacuum.out index c63a157e5f..a3209477a1 100644 --- a/src/test/regress/expected/vacuum.out +++ b/src/test/regress/expected/vacuum.out @@ -282,6 +282,19 @@ ALTER TABLE vactst ALTER COLUMN t SET STORAGE EXTERNAL; VACUUM (PROCESS_TOAST FALSE) vactst; VACUUM (PROCESS_TOAST FALSE, FULL) vactst; ERROR: PROCESS_TOAST required with VACUUM FULL +-- Single table inside transaction block +CREATE TEMPORARY TABLE vactst_temp (LIKE vactst); +BEGIN; +VACUUM FULL vactst_temp; +COMMIT; +BEGIN; +VACUUM vactst_temp; +ERROR: VACUUM cannot run inside a transaction block +COMMIT; +BEGIN; +VACUUM (ANALYZE) vactst; +NOTICE: autovacuum of "vactst" was requested, using the options specified +COMMIT; DROP TABLE vaccluster; DROP TABLE vactst; DROP TABLE vacparted; diff --git a/src/test/regress/sql/vacuum.sql b/src/test/regress/sql/vacuum.sql index 9faa8a34a6..6e489a2e40 100644 --- a/src/test/regress/sql/vacuum.sql +++ b/src/test/regress/sql/vacuum.sql @@ -237,6 +237,18 @@ ALTER TABLE vactst ALTER COLUMN t SET STORAGE EXTERNAL; VACUUM (PROCESS_TOAST FALSE) vactst; VACUUM (PROCESS_TOAST FALSE, FULL) vactst; +-- Single table inside transaction block +CREATE TEMPORARY TABLE vactst_temp (LIKE vactst); +BEGIN; +VACUUM FULL vactst_temp; +COMMIT; +BEGIN; +VACUUM vactst_temp; +COMMIT; +BEGIN; +VACUUM (ANALYZE) vactst; +COMMIT; + DROP TABLE vaccluster; DROP TABLE vactst; DROP TABLE vacparted;