1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-18 02:02:55 +03:00

Transaction IDs wrap around, per my proposal of 13-Aug-01. More

documentation to come, but the code is all here.  initdb forced.
This commit is contained in:
Tom Lane
2001-08-26 16:56:03 +00:00
parent d1ee78f296
commit bc7d37a525
29 changed files with 617 additions and 171 deletions

View File

@@ -13,7 +13,7 @@
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Header: /cvsroot/pgsql/src/backend/access/transam/clog.c,v 1.2 2001/08/25 23:24:39 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/access/transam/clog.c,v 1.3 2001/08/26 16:55:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -762,8 +762,12 @@ ExtendCLOG(TransactionId newestXact)
{
int pageno;
/* No work except at first XID of a page */
if (TransactionIdToPgIndex(newestXact) != 0)
/*
* No work except at first XID of a page. But beware: just after
* wraparound, the first XID of page zero is FirstNormalTransactionId.
*/
if (TransactionIdToPgIndex(newestXact) != 0 &&
!TransactionIdEquals(newestXact, FirstNormalTransactionId))
return;
pageno = TransactionIdToPage(newestXact);
@@ -818,6 +822,18 @@ TruncateCLOG(TransactionId oldestXact)
S_LOCK(&(ClogCtl->control_lck));
restart:;
/*
* While we are holding the lock, make an important safety check:
* the planned cutoff point must be <= the current CLOG endpoint page.
* Otherwise we have already wrapped around, and proceeding with the
* truncation would risk removing the current CLOG segment.
*/
if (CLOGPagePrecedes(ClogCtl->latest_page_number, cutoffPage))
{
S_UNLOCK(&(ClogCtl->control_lck));
elog(LOG, "unable to truncate commit log: apparent wraparound");
return;
}
for (slotno = 0; slotno < NUM_CLOG_BUFFERS; slotno++)
{

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/access/transam/transam.c,v 1.47 2001/08/25 18:52:41 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/access/transam/transam.c,v 1.48 2001/08/26 16:55:59 tgl Exp $
*
* NOTES
* This file contains the high level access-method interface to the
@@ -236,3 +236,68 @@ TransactionIdAbort(TransactionId transactionId)
TransactionLogUpdate(transactionId, TRANSACTION_STATUS_ABORTED);
}
/*
* TransactionIdPrecedes --- is id1 logically < id2?
*/
bool
TransactionIdPrecedes(TransactionId id1, TransactionId id2)
{
/*
* If either ID is a permanent XID then we can just do unsigned
* comparison. If both are normal, do a modulo-2^31 comparison.
*/
int32 diff;
if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))
return (id1 < id2);
diff = (int32) (id1 - id2);
return (diff < 0);
}
/*
* TransactionIdPrecedesOrEquals --- is id1 logically <= id2?
*/
bool
TransactionIdPrecedesOrEquals(TransactionId id1, TransactionId id2)
{
int32 diff;
if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))
return (id1 <= id2);
diff = (int32) (id1 - id2);
return (diff <= 0);
}
/*
* TransactionIdFollows --- is id1 logically > id2?
*/
bool
TransactionIdFollows(TransactionId id1, TransactionId id2)
{
int32 diff;
if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))
return (id1 > id2);
diff = (int32) (id1 - id2);
return (diff > 0);
}
/*
* TransactionIdFollowsOrEquals --- is id1 logically >= id2?
*/
bool
TransactionIdFollowsOrEquals(TransactionId id1, TransactionId id2)
{
int32 diff;
if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))
return (id1 >= id2);
diff = (int32) (id1 - id2);
return (diff >= 0);
}

View File

@@ -6,23 +6,18 @@
* Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: xid.c,v 1.32 2001/08/23 23:06:37 tgl Exp $
*
* OLD COMMENTS
* XXX WARNING
* Much of this file will change when we change our representation
* of transaction ids -cim 3/23/90
*
* It is time to make the switch from 5 byte to 4 byte transaction ids
* This file was totally reworked. -mer 5/22/92
* $Id: xid.c,v 1.33 2001/08/26 16:55:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <limits.h>
#include "access/xact.h"
#define PG_GETARG_TRANSACTIONID(n) DatumGetTransactionId(PG_GETARG_DATUM(n))
#define PG_RETURN_TRANSACTIONID(x) return TransactionIdGetDatum(x)
@@ -30,9 +25,9 @@
Datum
xidin(PG_FUNCTION_ARGS)
{
char *representation = PG_GETARG_CSTRING(0);
char *str = PG_GETARG_CSTRING(0);
PG_RETURN_TRANSACTIONID((TransactionId) atol(representation));
PG_RETURN_TRANSACTIONID((TransactionId) strtoul(str, NULL, 0));
}
Datum
@@ -40,21 +35,15 @@ xidout(PG_FUNCTION_ARGS)
{
TransactionId transactionId = PG_GETARG_TRANSACTIONID(0);
/* maximum 32 bit unsigned integer representation takes 10 chars */
char *representation = palloc(11);
char *str = palloc(11);
snprintf(representation, 11, "%lu", (unsigned long) transactionId);
snprintf(str, 11, "%lu", (unsigned long) transactionId);
PG_RETURN_CSTRING(representation);
PG_RETURN_CSTRING(str);
}
/* ----------------------------------------------------------------
* xideq
* ----------------------------------------------------------------
*/
/*
* xideq - returns 1, iff xid1 == xid2
* 0 else;
* xideq - are two xids equal?
*/
Datum
xideq(PG_FUNCTION_ARGS)
@@ -64,3 +53,19 @@ xideq(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(TransactionIdEquals(xid1, xid2));
}
/*
* xid_age - compute age of an XID (relative to current xact)
*/
Datum
xid_age(PG_FUNCTION_ARGS)
{
TransactionId xid = PG_GETARG_TRANSACTIONID(0);
TransactionId now = GetCurrentTransactionId();
/* Permanent XIDs are always infinitely old */
if (! TransactionIdIsNormal(xid))
PG_RETURN_INT32(INT_MAX);
PG_RETURN_INT32((int32) (now - xid));
}

View File

@@ -10,7 +10,7 @@
#
#
# IDENTIFICATION
# $Header: /cvsroot/pgsql/src/backend/catalog/Attic/genbki.sh,v 1.22 2001/08/24 14:07:48 petere Exp $
# $Header: /cvsroot/pgsql/src/backend/catalog/Attic/genbki.sh,v 1.23 2001/08/26 16:55:59 tgl Exp $
#
# NOTES
# non-essential whitespace is removed from the generated file.
@@ -155,12 +155,14 @@ INDEXMAXKEYS4=`expr $INDEXMAXKEYS '*' 4` || exit
touch ${OUTPUT_PREFIX}.description.$$
# ----------------
# strip comments and trash from .h before we generate
# the .bki file...
# Strip comments and other trash from .h
#
# Put multi-line start/end comments on a separate line
#
# Rename datatypes that have different names in .h files than in SQL
#
# Substitute values of configuration constants
# ----------------
# also, change Oid to oid. -- AY 8/94.
# also, change NameData to name. -- jolly 8/21/95.
# put multi-line start/end comments on a separate line
#
cat $INFILES | \
sed -e 's;/\*.*\*/;;g' \
@@ -173,11 +175,14 @@ sed -e 's;/\*.*\*/;;g' \
sed -e "s/;[ ]*$//g" \
-e "s/^[ ]*//" \
-e "s/[ ]Oid/ oid/g" \
-e "s/[ ]NameData/ name/g" \
-e "s/^Oid/oid/g" \
-e "s/(Oid/(oid/g" \
-e "s/[ ]NameData/ name/g" \
-e "s/^NameData/name/g" \
-e "s/(NameData/(name/g" \
-e "s/(Oid/(oid/g" \
-e "s/[ ]TransactionId/ xid/g" \
-e "s/^TransactionId/xid/g" \
-e "s/(TransactionId/(xid/g" \
-e "s/NAMEDATALEN/$NAMEDATALEN/g" \
-e "s/DEFAULT_ATTSTATTARGET/$DEFAULTATTSTATTARGET/g" \
-e "s/INDEX_MAX_KEYS\*2/$INDEXMAXKEYS2/g" \

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/catalog/index.c,v 1.162 2001/08/22 18:24:26 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/catalog/index.c,v 1.163 2001/08/26 16:55:59 tgl Exp $
*
*
* INTERFACE ROUTINES
@@ -1692,7 +1692,7 @@ IndexBuildHeapScan(Relation heapRelation,
TupleTableSlot *slot;
ExprContext *econtext;
Snapshot snapshot;
TransactionId XmaxRecent;
TransactionId OldestXmin;
/*
* sanity checks
@@ -1731,12 +1731,12 @@ IndexBuildHeapScan(Relation heapRelation,
if (IsBootstrapProcessingMode())
{
snapshot = SnapshotNow;
XmaxRecent = InvalidTransactionId;
OldestXmin = InvalidTransactionId;
}
else
{
snapshot = SnapshotAny;
GetXmaxRecent(&XmaxRecent);
OldestXmin = GetOldestXmin(heapRelation->rd_rel->relisshared);
}
scan = heap_beginscan(heapRelation, /* relation */
@@ -1769,7 +1769,7 @@ IndexBuildHeapScan(Relation heapRelation,
LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
sv_infomask = heapTuple->t_data->t_infomask;
switch (HeapTupleSatisfiesVacuum(heapTuple->t_data, XmaxRecent))
switch (HeapTupleSatisfiesVacuum(heapTuple->t_data, OldestXmin))
{
case HEAPTUPLE_DEAD:
indexIt = false;

View File

@@ -9,7 +9,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/dbcommands.c,v 1.78 2001/08/10 18:57:34 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/commands/dbcommands.c,v 1.79 2001/08/26 16:55:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -39,8 +39,9 @@
/* non-export function prototypes */
static bool get_db_info(const char *name, Oid *dbIdP, int4 *ownerIdP,
int *encodingP, bool *dbIsTemplateP,
Oid *dbLastSysOidP, char *dbpath);
int *encodingP, bool *dbIsTemplateP, Oid *dbLastSysOidP,
TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
char *dbpath);
static bool get_user_info(Oid use_sysid, bool *use_super, bool *use_createdb);
static char *resolve_alt_dbpath(const char *dbpath, Oid dboid);
static bool remove_dbdirs(const char *real_loc, const char *altloc);
@@ -65,6 +66,8 @@ createdb(const char *dbname, const char *dbpath,
int src_encoding;
bool src_istemplate;
Oid src_lastsysoid;
TransactionId src_vacuumxid;
TransactionId src_frozenxid;
char src_dbpath[MAXPGPATH];
Relation pg_database_rel;
HeapTuple tuple;
@@ -91,7 +94,7 @@ createdb(const char *dbname, const char *dbpath,
* idea, so accept possibility of race to create. We will check again
* after we grab the exclusive lock.
*/
if (get_db_info(dbname, NULL, NULL, NULL, NULL, NULL, NULL))
if (get_db_info(dbname, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
elog(ERROR, "CREATE DATABASE: database \"%s\" already exists", dbname);
/*
@@ -101,7 +104,9 @@ createdb(const char *dbname, const char *dbpath,
dbtemplate = "template1"; /* Default template database name */
if (!get_db_info(dbtemplate, &src_dboid, &src_owner, &src_encoding,
&src_istemplate, &src_lastsysoid, src_dbpath))
&src_istemplate, &src_lastsysoid,
&src_vacuumxid, &src_frozenxid,
src_dbpath))
elog(ERROR, "CREATE DATABASE: template \"%s\" does not exist",
dbtemplate);
@@ -208,8 +213,10 @@ createdb(const char *dbname, const char *dbpath,
pg_database_rel = heap_openr(DatabaseRelationName, AccessExclusiveLock);
/* Check to see if someone else created same DB name meanwhile. */
if (get_db_info(dbname, NULL, NULL, NULL, NULL, NULL, NULL))
if (get_db_info(dbname, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
{
/* Don't hold lock while doing recursive remove */
heap_close(pg_database_rel, AccessExclusiveLock);
remove_dbdirs(nominal_loc, alt_loc);
elog(ERROR, "CREATE DATABASE: database \"%s\" already exists", dbname);
}
@@ -227,6 +234,8 @@ createdb(const char *dbname, const char *dbpath,
new_record[Anum_pg_database_datistemplate - 1] = BoolGetDatum(false);
new_record[Anum_pg_database_datallowconn - 1] = BoolGetDatum(true);
new_record[Anum_pg_database_datlastsysoid - 1] = ObjectIdGetDatum(src_lastsysoid);
new_record[Anum_pg_database_datvacuumxid - 1] = TransactionIdGetDatum(src_vacuumxid);
new_record[Anum_pg_database_datfrozenxid - 1] = TransactionIdGetDatum(src_frozenxid);
/* no nulls here, GetRawDatabaseInfo doesn't like them */
new_record[Anum_pg_database_datpath - 1] =
DirectFunctionCall1(textin, CStringGetDatum(dbpath ? dbpath : ""));
@@ -307,7 +316,7 @@ dropdb(const char *dbname)
pgdbrel = heap_openr(DatabaseRelationName, AccessExclusiveLock);
if (!get_db_info(dbname, &db_id, &db_owner, NULL,
&db_istemplate, NULL, dbpath))
&db_istemplate, NULL, NULL, NULL, dbpath))
elog(ERROR, "DROP DATABASE: database \"%s\" does not exist", dbname);
if (!use_super && GetUserId() != db_owner)
@@ -397,13 +406,15 @@ dropdb(const char *dbname)
static bool
get_db_info(const char *name, Oid *dbIdP, int4 *ownerIdP,
int *encodingP, bool *dbIsTemplateP,
Oid *dbLastSysOidP, char *dbpath)
int *encodingP, bool *dbIsTemplateP, Oid *dbLastSysOidP,
TransactionId *dbVacuumXidP, TransactionId *dbFrozenXidP,
char *dbpath)
{
Relation relation;
ScanKeyData scanKey;
HeapScanDesc scan;
HeapTuple tuple;
bool gottuple;
AssertArg(name);
@@ -414,12 +425,11 @@ get_db_info(const char *name, Oid *dbIdP, int4 *ownerIdP,
F_NAMEEQ, NameGetDatum(name));
scan = heap_beginscan(relation, 0, SnapshotNow, 1, &scanKey);
if (!HeapScanIsValid(scan))
elog(ERROR, "Cannot begin scan of %s", DatabaseRelationName);
tuple = heap_getnext(scan, 0);
if (HeapTupleIsValid(tuple))
gottuple = HeapTupleIsValid(tuple);
if (gottuple)
{
Form_pg_database dbform = (Form_pg_database) GETSTRUCT(tuple);
text *tmptext;
@@ -428,7 +438,7 @@ get_db_info(const char *name, Oid *dbIdP, int4 *ownerIdP,
/* oid of the database */
if (dbIdP)
*dbIdP = tuple->t_data->t_oid;
/* uid of the owner */
/* sysid of the owner */
if (ownerIdP)
*ownerIdP = dbform->datdba;
/* multibyte encoding */
@@ -440,6 +450,12 @@ get_db_info(const char *name, Oid *dbIdP, int4 *ownerIdP,
/* last system OID used in database */
if (dbLastSysOidP)
*dbLastSysOidP = dbform->datlastsysoid;
/* limit of vacuumed XIDs */
if (dbVacuumXidP)
*dbVacuumXidP = dbform->datvacuumxid;
/* limit of frozen XIDs */
if (dbFrozenXidP)
*dbFrozenXidP = dbform->datfrozenxid;
/* database path (as registered in pg_database) */
if (dbpath)
{
@@ -462,7 +478,7 @@ get_db_info(const char *name, Oid *dbIdP, int4 *ownerIdP,
heap_endscan(scan);
heap_close(relation, AccessShareLock);
return HeapTupleIsValid(tuple);
return gottuple;
}
static bool

View File

@@ -13,7 +13,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.207 2001/08/10 18:57:35 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.208 2001/08/26 16:55:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -21,11 +21,13 @@
#include <unistd.h>
#include "access/clog.h"
#include "access/genam.h"
#include "access/heapam.h"
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/catname.h"
#include "catalog/pg_database.h"
#include "catalog/pg_index.h"
#include "commands/vacuum.h"
#include "executor/executor.h"
@@ -108,15 +110,24 @@ static MemoryContext vac_context = NULL;
static int MESSAGE_LEVEL; /* message level */
static TransactionId XmaxRecent;
static TransactionId OldestXmin;
static TransactionId FreezeLimit;
static TransactionId initialOldestXmin;
static TransactionId initialFreezeLimit;
/* non-export function prototypes */
static void vacuum_init(void);
static void vacuum_shutdown(void);
static void vacuum_init(VacuumStmt *vacstmt);
static void vacuum_shutdown(VacuumStmt *vacstmt);
static VRelList getrels(Name VacRelP, const char *stmttype);
static void vac_update_dbstats(Oid dbid,
TransactionId vacuumXID,
TransactionId frozenXID);
static void vac_truncate_clog(TransactionId vacuumXID,
TransactionId frozenXID);
static void vacuum_rel(Oid relid, VacuumStmt *vacstmt);
static void full_vacuum_rel(Relation onerel);
static void full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt);
static void scan_heap(VRelStats *vacrelstats, Relation onerel,
VacPageList vacuum_pages, VacPageList fraged_pages);
static void repair_frag(VRelStats *vacrelstats, Relation onerel,
@@ -213,7 +224,7 @@ vacuum(VacuumStmt *vacstmt)
/*
* Start up the vacuum cleaner.
*/
vacuum_init();
vacuum_init(vacstmt);
/*
* Process each selected relation. We are careful to process
@@ -230,21 +241,8 @@ vacuum(VacuumStmt *vacstmt)
analyze_rel(cur->vrl_relid, vacstmt);
}
/*
* If we did a complete vacuum, then flush the init file that relcache.c
* uses to save startup time. The next backend startup will rebuild the
* init file with up-to-date information from pg_class. This lets the
* optimizer see the stats that we've collected for certain critical
* system indexes. See relcache.c for more details.
*
* Ignore any failure to unlink the file, since it might not be there if
* no backend has been started since the last vacuum.
*/
if (vacstmt->vacrel == NULL)
unlink(RELCACHE_INIT_FILENAME);
/* clean up */
vacuum_shutdown();
vacuum_shutdown(vacstmt);
}
/*
@@ -268,20 +266,68 @@ vacuum(VacuumStmt *vacstmt)
* PostgresMain().
*/
static void
vacuum_init(void)
vacuum_init(VacuumStmt *vacstmt)
{
if (vacstmt->vacuum && vacstmt->vacrel == NULL)
{
/*
* 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();
}
static void
vacuum_shutdown(void)
vacuum_shutdown(VacuumStmt *vacstmt)
{
/* on entry, we are not in a transaction */
/* matches the CommitTransaction in PostgresMain() */
StartTransactionCommand();
/*
* If we did a database-wide VACUUM, update the database's pg_database
* row with info about the transaction IDs used, and try to truncate
* pg_clog.
*/
if (vacstmt->vacuum && vacstmt->vacrel == NULL)
{
vac_update_dbstats(MyDatabaseId,
initialOldestXmin, initialFreezeLimit);
vac_truncate_clog(initialOldestXmin, initialFreezeLimit);
}
/*
* If we did a complete vacuum or analyze, then flush the init file that
* relcache.c uses to save startup time. The next backend startup will
* rebuild the init file with up-to-date information from pg_class.
* This lets the optimizer see the stats that we've collected for certain
* critical system indexes. See relcache.c for more details.
*
* Ignore any failure to unlink the file, since it might not be there if
* no backend has been started since the last vacuum.
*/
if (vacstmt->vacrel == NULL)
unlink(RELCACHE_INIT_FILENAME);
/*
* Clean up working storage --- note we must do this after
* StartTransactionCommand, else we might be trying to delete the
@@ -382,6 +428,52 @@ getrels(Name VacRelP, const char *stmttype)
return vrl;
}
/*
* vacuum_set_xid_limits() -- compute oldest-Xmin and freeze cutoff points
*/
void
vacuum_set_xid_limits(VacuumStmt *vacstmt, bool sharedRel,
TransactionId *oldestXmin,
TransactionId *freezeLimit)
{
TransactionId limit;
*oldestXmin = GetOldestXmin(sharedRel);
Assert(TransactionIdIsNormal(*oldestXmin));
if (vacstmt->freeze)
{
/* FREEZE option: use oldest Xmin as freeze cutoff too */
limit = *oldestXmin;
}
else
{
/*
* Normal case: freeze cutoff is well in the past, to wit, about
* halfway to the wrap horizon
*/
limit = GetCurrentTransactionId() - (MaxTransactionId >> 2);
}
/*
* Be careful not to generate a "permanent" XID
*/
if (!TransactionIdIsNormal(limit))
limit = FirstNormalTransactionId;
/*
* Ensure sane relationship of limits
*/
if (TransactionIdFollows(limit, *oldestXmin))
{
elog(NOTICE, "oldest Xmin is far in the past --- close open transactions soon to avoid wraparound problems");
limit = *oldestXmin;
}
*freezeLimit = limit;
}
/*
* vac_update_relstats() -- update statistics for one relation
@@ -449,6 +541,122 @@ vac_update_relstats(Oid relid, BlockNumber num_pages, double num_tuples,
}
/*
* vac_update_dbstats() -- update statistics for one database
*
* Update the whole-database statistics that are kept in its pg_database
* row.
*
* We violate no-overwrite semantics here by storing new values for the
* statistics columns directly into the tuple that's already on the page.
* As with vac_update_relstats, this avoids leaving dead tuples behind
* after a VACUUM; which is good since GetRawDatabaseInfo
* can get confused by finding dead tuples in pg_database.
*
* This routine is shared by full and lazy VACUUM. Note that it is only
* applied after a database-wide VACUUM operation.
*/
static void
vac_update_dbstats(Oid dbid,
TransactionId vacuumXID,
TransactionId frozenXID)
{
Relation relation;
ScanKeyData entry[1];
HeapScanDesc scan;
HeapTuple tuple;
Form_pg_database dbform;
relation = heap_openr(DatabaseRelationName, RowExclusiveLock);
/* Must use a heap scan, since there's no syscache for pg_database */
ScanKeyEntryInitialize(&entry[0], 0x0,
ObjectIdAttributeNumber, F_OIDEQ,
ObjectIdGetDatum(dbid));
scan = heap_beginscan(relation, 0, SnapshotNow, 1, entry);
tuple = heap_getnext(scan, 0);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "database %u does not exist", dbid);
dbform = (Form_pg_database) GETSTRUCT(tuple);
/* overwrite the existing statistics in the tuple */
dbform->datvacuumxid = vacuumXID;
dbform->datfrozenxid = frozenXID;
/* invalidate the tuple in the cache and write the buffer */
RelationInvalidateHeapTuple(relation, tuple);
WriteNoReleaseBuffer(scan->rs_cbuf);
heap_endscan(scan);
heap_close(relation, RowExclusiveLock);
}
/*
* vac_truncate_clog() -- attempt to truncate the commit log
*
* Scan pg_database to determine the system-wide oldest datvacuumxid,
* and use it to truncate the transaction commit log (pg_clog).
* Also generate a warning if the system-wide oldest datfrozenxid
* seems to be in danger of wrapping around.
*
* The passed XIDs are simply the ones I just wrote into my pg_database
* entry. They're used to initialize the "min" calculations.
*
* This routine is shared by full and lazy VACUUM. Note that it is only
* applied after a database-wide VACUUM operation.
*/
static void
vac_truncate_clog(TransactionId vacuumXID, TransactionId frozenXID)
{
Relation relation;
HeapScanDesc scan;
HeapTuple tuple;
int32 age;
relation = heap_openr(DatabaseRelationName, AccessShareLock);
scan = heap_beginscan(relation, 0, SnapshotNow, 0, NULL);
while (HeapTupleIsValid(tuple = heap_getnext(scan, 0)))
{
Form_pg_database dbform = (Form_pg_database) GETSTRUCT(tuple);
/* Ignore non-connectable databases (eg, template0) */
/* It's assumed that these have been frozen correctly */
if (!dbform->datallowconn)
continue;
if (TransactionIdIsNormal(dbform->datvacuumxid) &&
TransactionIdPrecedes(dbform->datvacuumxid, vacuumXID))
vacuumXID = dbform->datvacuumxid;
if (TransactionIdIsNormal(dbform->datfrozenxid) &&
TransactionIdPrecedes(dbform->datfrozenxid, frozenXID))
frozenXID = dbform->datfrozenxid;
}
heap_endscan(scan);
heap_close(relation, AccessShareLock);
/* Truncate CLOG to the oldest vacuumxid */
TruncateCLOG(vacuumXID);
/* Give warning about impending wraparound problems */
age = (int32) (GetCurrentTransactionId() - frozenXID);
if (age > (int32) ((MaxTransactionId >> 3) * 3))
elog(NOTICE, "Some databases have not been vacuumed in %d transactions."
"\n\tBetter vacuum them within %d transactions,"
"\n\tor you may have a wraparound failure.",
age, (int32) (MaxTransactionId >> 1) - age);
}
/****************************************************************************
* *
* Code common to both flavors of VACUUM *
@@ -550,7 +758,7 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt)
* Do the actual work --- either FULL or "lazy" vacuum
*/
if (vacstmt->full)
full_vacuum_rel(onerel);
full_vacuum_rel(onerel, vacstmt);
else
lazy_vacuum_rel(onerel, vacstmt);
@@ -597,7 +805,7 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt)
* and locked the relation.
*/
static void
full_vacuum_rel(Relation onerel)
full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt)
{
VacPageListData vacuum_pages; /* List of pages to vacuum and/or
* clean indexes */
@@ -613,7 +821,8 @@ full_vacuum_rel(Relation onerel)
IsSystemRelationName(RelationGetRelationName(onerel)))
reindex = true;
GetXmaxRecent(&XmaxRecent);
vacuum_set_xid_limits(vacstmt, onerel->rd_rel->relisshared,
&OldestXmin, &FreezeLimit);
/*
* Set up statistics-gathering machinery.
@@ -845,12 +1054,25 @@ scan_heap(VRelStats *vacrelstats, Relation onerel,
tupgone = false;
sv_infomask = tuple.t_data->t_infomask;
switch (HeapTupleSatisfiesVacuum(tuple.t_data, XmaxRecent))
switch (HeapTupleSatisfiesVacuum(tuple.t_data, OldestXmin))
{
case HEAPTUPLE_DEAD:
tupgone = true; /* we can delete the tuple */
break;
case HEAPTUPLE_LIVE:
/*
* Tuple is good. Consider whether to replace its xmin
* value with FrozenTransactionId.
*/
if (TransactionIdIsNormal(tuple.t_data->t_xmin) &&
TransactionIdPrecedes(tuple.t_data->t_xmin,
FreezeLimit))
{
tuple.t_data->t_xmin = FrozenTransactionId;
tuple.t_data->t_infomask &= ~HEAP_XMIN_INVALID;
tuple.t_data->t_infomask |= HEAP_XMIN_COMMITTED;
pgchanged = true;
}
break;
case HEAPTUPLE_RECENTLY_DEAD:
/*
@@ -1312,7 +1534,7 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
* tuples to another places.
*/
if ((tuple.t_data->t_infomask & HEAP_UPDATED &&
!TransactionIdPrecedes(tuple.t_data->t_xmin, XmaxRecent)) ||
!TransactionIdPrecedes(tuple.t_data->t_xmin, OldestXmin)) ||
(!(tuple.t_data->t_infomask & HEAP_XMAX_INVALID) &&
!(ItemPointerEquals(&(tuple.t_self),
&(tuple.t_data->t_ctid)))))
@@ -1362,7 +1584,7 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
/*
* This means that in the middle of chain there
* was tuple updated by older (than XmaxRecent)
* was tuple updated by older (than OldestXmin)
* xaction and this tuple is already deleted by
* me. Actually, upper part of chain should be
* removed and seems that this should be handled
@@ -1430,7 +1652,7 @@ repair_frag(VRelStats *vacrelstats, Relation onerel,
/* All done ? */
if (!(tp.t_data->t_infomask & HEAP_UPDATED) ||
TransactionIdPrecedes(tp.t_data->t_xmin, XmaxRecent))
TransactionIdPrecedes(tp.t_data->t_xmin, OldestXmin))
break;
/* Well, try to find tuple with old row version */

View File

@@ -31,7 +31,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/commands/vacuumlazy.c,v 1.4 2001/08/10 18:57:35 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/commands/vacuumlazy.c,v 1.5 2001/08/26 16:55:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -94,7 +94,8 @@ typedef struct LVRelStats
static int MESSAGE_LEVEL; /* message level */
static TransactionId XmaxRecent;
static TransactionId OldestXmin;
static TransactionId FreezeLimit;
/* non-export function prototypes */
@@ -143,7 +144,8 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt)
else
MESSAGE_LEVEL = DEBUG;
GetXmaxRecent(&XmaxRecent);
vacuum_set_xid_limits(vacstmt, onerel->rd_rel->relisshared,
&OldestXmin, &FreezeLimit);
vacrelstats = (LVRelStats *) palloc(sizeof(LVRelStats));
MemSet(vacrelstats, 0, sizeof(LVRelStats));
@@ -307,12 +309,25 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
tupgone = false;
sv_infomask = tuple.t_data->t_infomask;
switch (HeapTupleSatisfiesVacuum(tuple.t_data, XmaxRecent))
switch (HeapTupleSatisfiesVacuum(tuple.t_data, OldestXmin))
{
case HEAPTUPLE_DEAD:
tupgone = true; /* we can delete the tuple */
break;
case HEAPTUPLE_LIVE:
/*
* Tuple is good. Consider whether to replace its xmin
* value with FrozenTransactionId.
*/
if (TransactionIdIsNormal(tuple.t_data->t_xmin) &&
TransactionIdPrecedes(tuple.t_data->t_xmin,
FreezeLimit))
{
tuple.t_data->t_xmin = FrozenTransactionId;
tuple.t_data->t_infomask &= ~HEAP_XMIN_INVALID;
tuple.t_data->t_infomask |= HEAP_XMIN_COMMITTED;
pgchanged = true;
}
break;
case HEAPTUPLE_RECENTLY_DEAD:
/*
@@ -783,12 +798,13 @@ count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats)
tupgone = false;
sv_infomask = tuple.t_data->t_infomask;
switch (HeapTupleSatisfiesVacuum(tuple.t_data, XmaxRecent))
switch (HeapTupleSatisfiesVacuum(tuple.t_data, OldestXmin))
{
case HEAPTUPLE_DEAD:
tupgone = true; /* we can delete the tuple */
break;
case HEAPTUPLE_LIVE:
/* Shouldn't be necessary to re-freeze anything */
break;
case HEAPTUPLE_RECENTLY_DEAD:
/*

View File

@@ -15,7 +15,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.154 2001/08/21 16:36:02 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.155 2001/08/26 16:55:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -2246,6 +2246,7 @@ _copyVacuumStmt(VacuumStmt *from)
newnode->vacuum = from->vacuum;
newnode->full = from->full;
newnode->analyze = from->analyze;
newnode->freeze = from->freeze;
newnode->verbose = from->verbose;
if (from->vacrel)
newnode->vacrel = pstrdup(from->vacrel);

View File

@@ -20,7 +20,7 @@
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.102 2001/08/21 16:36:02 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.103 2001/08/26 16:55:59 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1116,6 +1116,8 @@ _equalVacuumStmt(VacuumStmt *a, VacuumStmt *b)
return false;
if (a->analyze != b->analyze)
return false;
if (a->freeze != b->freeze)
return false;
if (a->verbose != b->verbose)
return false;
if (!equalstr(a->vacrel, b->vacrel))

View File

@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.248 2001/08/25 18:52:41 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.249 2001/08/26 16:55:59 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -215,7 +215,7 @@ static void doNegateFloat(Value *v);
%type <boolean> opt_binary, opt_using, opt_instead, opt_cursor
%type <boolean> opt_with_copy, index_opt_unique, opt_verbose, opt_full
%type <boolean> analyze_keyword
%type <boolean> opt_freeze, analyze_keyword
%type <ival> copy_dirn, direction, reindex_type, drop_type,
opt_column, event, comment_type, comment_cl,
@@ -346,7 +346,7 @@ static void doNegateFloat(Value *v);
CACHE, CHECKPOINT, CLUSTER, COMMENT, COPY, CREATEDB, CREATEUSER, CYCLE,
DATABASE, DELIMITERS, DO,
EACH, ENCODING, EXCLUSIVE, EXPLAIN,
FORCE, FORWARD, FUNCTION, HANDLER,
FORCE, FORWARD, FREEZE, FUNCTION, HANDLER,
ILIKE, INCREMENT, INDEX, INHERITS, INSTEAD, ISNULL,
LANCOMPILER, LIMIT, LISTEN, LOAD, LOCATION, LOCK_P,
MAXVALUE, MINVALUE, MODE, MOVE,
@@ -3082,34 +3082,37 @@ ClusterStmt: CLUSTER index_name ON relation_name
*
*****************************************************************************/
VacuumStmt: VACUUM opt_full opt_verbose
VacuumStmt: VACUUM opt_full opt_freeze opt_verbose
{
VacuumStmt *n = makeNode(VacuumStmt);
n->vacuum = true;
n->analyze = false;
n->full = $2;
n->verbose = $3;
n->freeze = $3;
n->verbose = $4;
n->vacrel = NULL;
n->va_cols = NIL;
$$ = (Node *)n;
}
| VACUUM opt_full opt_verbose relation_name
| VACUUM opt_full opt_freeze opt_verbose relation_name
{
VacuumStmt *n = makeNode(VacuumStmt);
n->vacuum = true;
n->analyze = false;
n->full = $2;
n->verbose = $3;
n->vacrel = $4;
n->freeze = $3;
n->verbose = $4;
n->vacrel = $5;
n->va_cols = NIL;
$$ = (Node *)n;
}
| VACUUM opt_full opt_verbose AnalyzeStmt
| VACUUM opt_full opt_freeze opt_verbose AnalyzeStmt
{
VacuumStmt *n = (VacuumStmt *) $4;
VacuumStmt *n = (VacuumStmt *) $5;
n->vacuum = true;
n->full = $2;
n->verbose |= $3;
n->freeze = $3;
n->verbose |= $4;
$$ = (Node *)n;
}
;
@@ -3120,6 +3123,7 @@ AnalyzeStmt: analyze_keyword opt_verbose
n->vacuum = false;
n->analyze = true;
n->full = false;
n->freeze = false;
n->verbose = $2;
n->vacrel = NULL;
n->va_cols = NIL;
@@ -3131,6 +3135,7 @@ AnalyzeStmt: analyze_keyword opt_verbose
n->vacuum = false;
n->analyze = true;
n->full = false;
n->freeze = false;
n->verbose = $2;
n->vacrel = $3;
n->va_cols = $4;
@@ -3150,6 +3155,10 @@ opt_full: FULL { $$ = TRUE; }
| /*EMPTY*/ { $$ = FALSE; }
;
opt_freeze: FREEZE { $$ = TRUE; }
| /*EMPTY*/ { $$ = FALSE; }
;
opt_name_list: '(' name_list ')' { $$ = $2; }
| /*EMPTY*/ { $$ = NIL; }
;
@@ -5615,6 +5624,7 @@ TokenId: ABSOLUTE { $$ = "absolute"; }
| DROP { $$ = "drop"; }
| EACH { $$ = "each"; }
| ENCODING { $$ = "encoding"; }
| ENCRYPTED { $$ = "encrypted"; }
| ESCAPE { $$ = "escape"; }
| EXCLUSIVE { $$ = "exclusive"; }
| EXECUTE { $$ = "execute"; }
@@ -5693,6 +5703,7 @@ TokenId: ABSOLUTE { $$ = "absolute"; }
| TRUNCATE { $$ = "truncate"; }
| TRUSTED { $$ = "trusted"; }
| TYPE_P { $$ = "type"; }
| UNENCRYPTED { $$ = "unencrypted"; }
| UNLISTEN { $$ = "unlisten"; }
| UNTIL { $$ = "until"; }
| UPDATE { $$ = "update"; }
@@ -5753,7 +5764,6 @@ ColLabel: ColId { $$ = $1; }
| DISTINCT { $$ = "distinct"; }
| DO { $$ = "do"; }
| ELSE { $$ = "else"; }
| ENCRYPTED { $$ = "encrypted"; }
| END_TRANS { $$ = "end"; }
| EXCEPT { $$ = "except"; }
| EXISTS { $$ = "exists"; }
@@ -5763,6 +5773,7 @@ ColLabel: ColId { $$ = $1; }
| FLOAT { $$ = "float"; }
| FOR { $$ = "for"; }
| FOREIGN { $$ = "foreign"; }
| FREEZE { $$ = "freeze"; }
| FROM { $$ = "from"; }
| FULL { $$ = "full"; }
| GLOBAL { $$ = "global"; }
@@ -5825,7 +5836,6 @@ ColLabel: ColId { $$ = $1; }
| TRANSACTION { $$ = "transaction"; }
| TRIM { $$ = "trim"; }
| TRUE_P { $$ = "true"; }
| UNENCRYPTED { $$ = "unencrypted"; }
| UNION { $$ = "union"; }
| UNIQUE { $$ = "unique"; }
| UNKNOWN { $$ = "unknown"; }

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.96 2001/08/16 20:38:54 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/parser/keywords.c,v 1.97 2001/08/26 16:56:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -118,6 +118,7 @@ static ScanKeyword ScanKeywords[] = {
{"force", FORCE},
{"foreign", FOREIGN},
{"forward", FORWARD},
{"freeze", FREEZE},
{"from", FROM},
{"full", FULL},
{"function", FUNCTION},

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/storage/ipc/sinval.c,v 1.39 2001/08/25 18:52:42 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/storage/ipc/sinval.c,v 1.40 2001/08/26 16:56:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -20,6 +20,8 @@
#include "storage/sinval.h"
#include "storage/sinvaladt.h"
#include "utils/tqual.h"
#include "miscadmin.h"
SPINLOCK SInvalLock = (SPINLOCK) NULL;
@@ -210,17 +212,23 @@ TransactionIdIsInProgress(TransactionId xid)
}
/*
* GetXmaxRecent -- returns oldest transaction that was running
* when all current transaction were started.
* It's used by vacuum to decide what deleted
* tuples must be preserved in a table.
* GetOldestXmin -- returns oldest transaction that was running
* when any current transaction was started.
*
* Note: we include all currently running xids in the set of considered xids.
* If allDbs is TRUE then all backends are considered; if allDbs is FALSE
* then only backends running in my own database are considered.
*
* This is used by VACUUM to decide which deleted tuples must be preserved
* in a table. allDbs = TRUE is needed for shared relations, but allDbs =
* FALSE is sufficient for non-shared relations, since only backends in my
* own database could ever see the tuples in them.
*
* Note: we include the currently running xids in the set of considered xids.
* This ensures that if a just-started xact has not yet set its snapshot,
* when it does set the snapshot it cannot set xmin less than what we compute.
*/
void
GetXmaxRecent(TransactionId *XmaxRecent)
TransactionId
GetOldestXmin(bool allDbs)
{
SISeg *segP = shmInvalBuffer;
ProcState *stateP = segP->procState;
@@ -238,24 +246,28 @@ GetXmaxRecent(TransactionId *XmaxRecent)
if (pOffset != INVALID_OFFSET)
{
PROC *proc = (PROC *) MAKE_PTR(pOffset);
/* Fetch xid just once - see GetNewTransactionId */
TransactionId xid = proc->xid;
if (TransactionIdIsNormal(xid))
if (allDbs || proc->databaseId == MyDatabaseId)
{
if (TransactionIdPrecedes(xid, result))
result = xid;
xid = proc->xmin;
/* Fetch xid just once - see GetNewTransactionId */
TransactionId xid = proc->xid;
if (TransactionIdIsNormal(xid))
{
if (TransactionIdPrecedes(xid, result))
result = xid;
xid = proc->xmin;
if (TransactionIdIsNormal(xid))
if (TransactionIdPrecedes(xid, result))
result = xid;
}
}
}
}
SpinRelease(SInvalLock);
*XmaxRecent = result;
return result;
}
/*----------

View File

@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/time/tqual.c,v 1.41 2001/08/25 18:52:42 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/time/tqual.c,v 1.42 2001/08/26 16:56:00 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -592,8 +592,8 @@ HeapTupleSatisfiesSnapshot(HeapTupleHeader tuple, Snapshot snapshot)
* HeapTupleSatisfiesVacuum - determine tuple status for VACUUM and related
* operations
*
* XmaxRecent is a cutoff XID (obtained from GetXmaxRecent()). Tuples
* deleted by XIDs >= XmaxRecent are deemed "recently dead"; they might
* OldestXmin is a cutoff XID (obtained from GetOldestXmin()). Tuples
* deleted by XIDs >= OldestXmin are deemed "recently dead"; they might
* still be visible to some open transaction, so we can't remove them,
* even if we see that the deleting transaction has committed.
*
@@ -603,7 +603,7 @@ HeapTupleSatisfiesSnapshot(HeapTupleHeader tuple, Snapshot snapshot)
* change in t_infomask and scheduling a disk write if so.
*/
HTSV_Result
HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId XmaxRecent)
HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId OldestXmin)
{
/*
* Has inserting transaction committed?
@@ -712,7 +712,7 @@ HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId XmaxRecent)
return HEAPTUPLE_DEAD;
}
if (!TransactionIdPrecedes(tuple->t_xmax, XmaxRecent))
if (!TransactionIdPrecedes(tuple->t_xmax, OldestXmin))
{
/* deleting xact is too recent, tuple could still be visible */
return HEAPTUPLE_RECENTLY_DEAD;