]> granicus.if.org Git - postgresql/commitdiff
For multi-table ANALYZE, use per-table transactions when possible
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 22 May 2004 23:14:38 +0000 (23:14 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 22 May 2004 23:14:38 +0000 (23:14 +0000)
(ie, when not inside a transaction block), so that we can avoid holding
locks longer than necessary.  Per trouble report from Philip Warner.

src/backend/access/transam/xact.c
src/backend/commands/vacuum.c
src/include/access/xact.h

index b29ccdf5f76327f61b36d2ff156b11ac641afe8f..1764000e5e54dfb4f48da7943da96f8ae57b7622 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.166 2004/05/21 05:07:56 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.167 2004/05/22 23:14:37 tgl Exp $
  *
  * NOTES
  *             Transaction aborts can now occur two ways:
@@ -1417,7 +1417,7 @@ PreventTransactionChain(void *stmtNode, const char *stmtType)
                         errmsg("%s cannot be executed from a function", stmtType)));
        /* If we got past IsTransactionBlock test, should be in default state */
        if (CurrentTransactionState->blockState != TBLOCK_DEFAULT &&
-                       CurrentTransactionState->blockState != TBLOCK_STARTED)
+               CurrentTransactionState->blockState != TBLOCK_STARTED)
                elog(ERROR, "cannot prevent transaction chain");
        /* all okay */
 }
@@ -1462,6 +1462,36 @@ RequireTransactionChain(void *stmtNode, const char *stmtType)
                                        stmtType)));
 }
 
+/*
+ *     IsInTransactionChain
+ *
+ *     This routine is for statements that need to behave differently inside
+ *     a transaction block than when running as single commands.  ANALYZE is
+ *     currently the only example.
+ *
+ *     stmtNode: pointer to parameter block for statement; this is used in
+ *     a very klugy way to determine whether we are inside a function.
+ */
+bool
+IsInTransactionChain(void *stmtNode)
+{
+       /*
+        * Return true on same conditions that would make PreventTransactionChain
+        * error out
+        */
+       if (IsTransactionBlock())
+               return true;
+
+       if (!MemoryContextContains(QueryContext, stmtNode))
+               return true;
+
+       if (CurrentTransactionState->blockState != TBLOCK_DEFAULT &&
+               CurrentTransactionState->blockState != TBLOCK_STARTED)
+               return true;
+
+       return false;
+}
+
 
 /*
  * Register or deregister callback functions for end-of-xact cleanup
index 8e4f2a328e68f01955bcab7b01f0204e5b7e197b..5822b7f210cbc42ea2d88d70f00594e3884c6ed2 100644 (file)
@@ -13,7 +13,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.276 2004/05/21 16:08:46 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.277 2004/05/22 23:14:38 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -161,7 +161,9 @@ vacuum(VacuumStmt *vacstmt)
        MemoryContext anl_context = NULL;
        TransactionId initialOldestXmin = InvalidTransactionId;
        TransactionId initialFreezeLimit = InvalidTransactionId;
-       bool            all_rels;
+       bool            all_rels,
+                               in_outer_xact,
+                               use_own_xacts;
        List       *relations,
                           *cur;
 
@@ -177,10 +179,23 @@ vacuum(VacuumStmt *vacstmt)
         * Furthermore, the forced commit that occurs before truncating the
         * relation's file would have the effect of committing the rest of the
         * user's transaction too, which would certainly not be the desired
-        * behavior.
+        * behavior.  (This only applies to VACUUM FULL, though.  We could
+        * in theory run lazy VACUUM inside a transaction block, but we choose
+        * to disallow that case because we'd rather commit as soon as possible
+        * after finishing the vacuum.  This is mainly so that we can let go the
+        * AccessExclusiveLock that we may be holding.)
+        *
+        * ANALYZE (without VACUUM) can run either way.
         */
        if (vacstmt->vacuum)
+       {
                PreventTransactionChain((void *) vacstmt, stmttype);
+               in_outer_xact = false;
+       }
+       else
+       {
+               in_outer_xact = IsInTransactionChain((void *) vacstmt);
+       }
 
        /* Turn vacuum cost accounting on or off */
        VacuumCostActive = (VacuumCostNaptime > 0);
@@ -205,81 +220,89 @@ vacuum(VacuumStmt *vacstmt)
                                                                                ALLOCSET_DEFAULT_INITSIZE,
                                                                                ALLOCSET_DEFAULT_MAXSIZE);
 
-       /*
-        * If we are running only ANALYZE, we don't need per-table
-        * transactions, but we still need a memory context with table
-        * lifetime.
-        */
-       if (vacstmt->analyze && !vacstmt->vacuum)
-               anl_context = AllocSetContextCreate(PortalContext,
-                                                                                       "Analyze",
-                                                                                       ALLOCSET_DEFAULT_MINSIZE,
-                                                                                       ALLOCSET_DEFAULT_INITSIZE,
-                                                                                       ALLOCSET_DEFAULT_MAXSIZE);
-
        /* Assume we are processing everything unless one table is mentioned */
        all_rels = (vacstmt->relation == NULL);
 
        /* Build list of relations to process (note this lives in vac_context) */
        relations = get_rel_oids(vacstmt->relation, stmttype);
 
+       if (vacstmt->vacuum && all_rels)
+       {
+               /*
+                * It's a database-wide VACUUM.
+                *
+                * Compute the initially applicable OldestXmin and FreezeLimit
+                * XIDs, so that we can record these values at the end of the
+                * VACUUM. Note that individual tables may well be processed
+                * with newer values, but we can guarantee that no
+                * (non-shared) relations are processed with older ones.
+                *
+                * It is okay to record non-shared values in pg_database, even
+                * though we may vacuum shared relations with older cutoffs,
+                * because only the minimum of the values present in
+                * pg_database matters.  We can be sure that shared relations
+                * have at some time been vacuumed with cutoffs no worse than
+                * the global minimum; for, if there is a backend in some
+                * other DB with xmin = OLDXMIN that's determining the cutoff
+                * with which we vacuum shared relations, it is not possible
+                * for that database to have a cutoff newer than OLDXMIN
+                * recorded in pg_database.
+                */
+               vacuum_set_xid_limits(vacstmt, false,
+                                                         &initialOldestXmin,
+                                                         &initialFreezeLimit);
+       }
+
        /*
-        * Formerly, there was code here to prevent more than one VACUUM from
-        * executing concurrently in the same database.  However, there's no
-        * good reason to prevent that, and manually removing lockfiles after
-        * a vacuum crash was a pain for dbadmins.      So, forget about
-        * lockfiles, and just rely on the locks we grab on each target table
-        * to ensure that there aren't two VACUUMs running on the same table
-        * at the same time.
+        * 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.)
+        *
+        * 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 only processing one relation.  For multiple relations when not
+        * within a transaction block, use own transactions so we can release
+        * locks sooner.
         */
+       if (vacstmt->vacuum)
+       {
+               use_own_xacts = true;
+       }
+       else
+       {
+               Assert(vacstmt->analyze);
+               if (in_outer_xact)
+                       use_own_xacts = false;
+               else if (length(relations) > 1)
+                       use_own_xacts = true;
+               else
+                       use_own_xacts = false;
+       }
+
+       /*
+        * If we are running ANALYZE without per-table transactions, we'll
+        * need a memory context with table lifetime.
+        */
+       if (!use_own_xacts)
+               anl_context = AllocSetContextCreate(PortalContext,
+                                                                                       "Analyze",
+                                                                                       ALLOCSET_DEFAULT_MINSIZE,
+                                                                                       ALLOCSET_DEFAULT_INITSIZE,
+                                                                                       ALLOCSET_DEFAULT_MAXSIZE);
 
        /*
-        * The strangeness with committing and starting transactions here is
-        * due to wanting to run each table's VACUUM as a separate
-        * transaction, so that we don't hold locks unnecessarily long.  Also,
-        * if we are doing VACUUM ANALYZE, the ANALYZE part runs as a separate
-        * transaction from the VACUUM to further reduce locking.
-        *
         * 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
         * commit the transaction started in PostgresMain() here, and start
         * another one before exiting to match the commit waiting for us back
         * in PostgresMain().
-        *
-        * In the case of an ANALYZE statement (no vacuum, just analyze) it's
-        * okay to run the whole thing in the outer transaction, and so we
-        * skip transaction start/stop operations.
         */
-       if (vacstmt->vacuum)
+       if (use_own_xacts)
        {
-               if (all_rels)
-               {
-                       /*
-                        * It's a database-wide VACUUM.
-                        *
-                        * Compute the initially applicable OldestXmin and FreezeLimit
-                        * XIDs, so that we can record these values at the end of the
-                        * VACUUM. Note that individual tables may well be processed
-                        * with newer values, but we can guarantee that no
-                        * (non-shared) relations are processed with older ones.
-                        *
-                        * It is okay to record non-shared values in pg_database, even
-                        * though we may vacuum shared relations with older cutoffs,
-                        * because only the minimum of the values present in
-                        * pg_database matters.  We can be sure that shared relations
-                        * have at some time been vacuumed with cutoffs no worse than
-                        * the global minimum; for, if there is a backend in some
-                        * other DB with xmin = OLDXMIN that's determining the cutoff
-                        * with which we vacuum shared relations, it is not possible
-                        * for that database to have a cutoff newer than OLDXMIN
-                        * recorded in pg_database.
-                        */
-                       vacuum_set_xid_limits(vacstmt, false,
-                                                                 &initialOldestXmin,
-                                                                 &initialFreezeLimit);
-               }
-
                /* matches the StartTransaction in PostgresMain() */
                CommitTransactionCommand();
        }
@@ -301,13 +324,13 @@ vacuum(VacuumStmt *vacstmt)
                        MemoryContext old_context = NULL;
 
                        /*
-                        * If we vacuumed, use new transaction for analyze. Otherwise,
+                        * If using separate xacts, start one for analyze. Otherwise,
                         * we can use the outer transaction, but we still need to call
                         * analyze_rel in a memory context that will be cleaned up on
                         * return (else we leak memory while processing multiple
                         * tables).
                         */
-                       if (vacstmt->vacuum)
+                       if (use_own_xacts)
                        {
                                StartTransactionCommand();
                                SetQuerySnapshot();             /* might be needed for functions
@@ -326,7 +349,7 @@ vacuum(VacuumStmt *vacstmt)
 
                        StrategyHintVacuum(false);
 
-                       if (vacstmt->vacuum)
+                       if (use_own_xacts)
                                CommitTransactionCommand();
                        else
                        {
@@ -339,7 +362,7 @@ vacuum(VacuumStmt *vacstmt)
        /*
         * Finish up processing.
         */
-       if (vacstmt->vacuum)
+       if (use_own_xacts)
        {
                /* here, we are not in a transaction */
 
@@ -348,7 +371,10 @@ vacuum(VacuumStmt *vacstmt)
                 * PostgresMain().
                 */
                StartTransactionCommand();
+       }
 
+       if (vacstmt->vacuum)
+       {
                /*
                 * If it was a database-wide VACUUM, print FSM usage statistics
                 * (we don't make you be superuser to see these).
index 3eb334b30e37795ae307640e88879b4ed4adee76..53a585ec6947f256f19dacc58ccfd1120fe22419 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/access/xact.h,v 1.62 2004/04/05 03:11:39 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/access/xact.h,v 1.63 2004/05/22 23:14:38 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -145,6 +145,7 @@ extern void UserAbortTransactionBlock(void);
 extern void AbortOutOfAnyTransaction(void);
 extern void PreventTransactionChain(void *stmtNode, const char *stmtType);
 extern void RequireTransactionChain(void *stmtNode, const char *stmtType);
+extern bool IsInTransactionChain(void *stmtNode);
 extern void RegisterEOXactCallback(EOXactCallback callback, void *arg);
 extern void UnregisterEOXactCallback(EOXactCallback callback, void *arg);