bool            dirty = false;
 
        /*
-        * Initialize the "min" calculation with RecentGlobalXmin.      Any
-        * not-yet-committed pg_class entries for new tables must have
-        * relfrozenxid at least this high, because any other open xact must have
-        * RecentXmin >= its PGPROC.xmin >= our RecentGlobalXmin; see
-        * AddNewRelationTuple().  So we cannot produce a wrong minimum by
-        * starting with this.
+        * Initialize the "min" calculation with GetOldestXmin, which is a
+        * reasonable approximation to the minimum relfrozenxid for not-yet-
+        * committed pg_class entries for new tables; see AddNewRelationTuple().
+        * Se we cannot produce a wrong minimum by starting with this.
         */
-       newFrozenXid = RecentGlobalXmin;
+       newFrozenXid = GetOldestXmin(true, true);
 
        /*
         * We must seqscan pg_class to find the minimum Xid, because there is no
        /* Begin a transaction for vacuuming this relation */
        StartTransactionCommand();
 
-       if (vacstmt->full)
-       {
-               /* functions in indexes may want a snapshot set */
-               PushActiveSnapshot(GetTransactionSnapshot());
-       }
-       else
+       /*
+        * Functions in indexes may want a snapshot set.  Also, setting
+        * a snapshot ensures that RecentGlobalXmin is kept truly recent.
+        */
+       PushActiveSnapshot(GetTransactionSnapshot());
+
+       if (!vacstmt->full)
        {
                /*
-                * During a lazy VACUUM we do not run any user-supplied functions, and
-                * so it should be safe to not create a transaction snapshot.
-                *
-                * We can furthermore set the PROC_IN_VACUUM flag, which lets other
+                * In lazy vacuum, we can set the PROC_IN_VACUUM flag, which lets other
                 * concurrent VACUUMs know that they can ignore this one while
                 * determining their OldestXmin.  (The reason we don't set it during a
                 * full VACUUM is exactly that we may have to run user- defined
 
        if (!onerel)
        {
-               if (vacstmt->full)
-                       PopActiveSnapshot();
+               PopActiveSnapshot();
                CommitTransactionCommand();
                return;
        }
                                        (errmsg("skipping \"%s\" --- only table or database owner can vacuum it",
                                                        RelationGetRelationName(onerel))));
                relation_close(onerel, lmode);
-               if (vacstmt->full)
-                       PopActiveSnapshot();
+               PopActiveSnapshot();
                CommitTransactionCommand();
                return;
        }
                                (errmsg("skipping \"%s\" --- cannot vacuum indexes, views, or special system tables",
                                                RelationGetRelationName(onerel))));
                relation_close(onerel, lmode);
-               if (vacstmt->full)
-                       PopActiveSnapshot();
+               PopActiveSnapshot();
                CommitTransactionCommand();
                return;
        }
        if (isOtherTempNamespace(RelationGetNamespace(onerel)))
        {
                relation_close(onerel, lmode);
-               if (vacstmt->full)
-                       PopActiveSnapshot();
+               PopActiveSnapshot();
                CommitTransactionCommand();
                return;
        }
        /*
         * Complete the transaction and free all temporary memory used.
         */
-       if (vacstmt->full)
-               PopActiveSnapshot();
+       PopActiveSnapshot();
        CommitTransactionCommand();
 
        /*
 
 #include "utils/plancache.h"
 #include "utils/portal.h"
 #include "utils/relcache.h"
+#include "utils/snapmgr.h"
 #include "utils/syscache.h"
 #include "utils/tqual.h"
 
        on_shmem_exit(ShutdownPostgres, 0);
 
        /*
-        * Start a new transaction here before first access to db
+        * Start a new transaction here before first access to db, and get a
+        * snapshot.  We don't have a use for the snapshot itself, but we're
+        * interested in the secondary effect that it sets RecentGlobalXmin.
         */
        if (!bootstrap)
+       {
                StartTransactionCommand();
+               (void) GetTransactionSnapshot();
+       }
 
        /*
         * Now that we have a transaction, we can take locks.  Take a writer's
 
  * These are updated by GetSnapshotData.  We initialize them this way
  * for the convenience of TransactionIdIsInProgress: even in bootstrap
  * mode, we don't want it to say that BootstrapTransactionId is in progress.
+ *
+ * RecentGlobalXmin is initialized to InvalidTransactionId, to ensure that no
+ * one tries to use a stale value.  Readers should ensure that it has been set
+ * to something else before using it.
  */
 TransactionId TransactionXmin = FirstNormalTransactionId;
 TransactionId RecentXmin = FirstNormalTransactionId;
-TransactionId RecentGlobalXmin = FirstNormalTransactionId;
+TransactionId RecentGlobalXmin = InvalidTransactionId;
 
 /*
  * Elements of the list of registered snapshots.