vacuum.c

来自「PostgreSQL7.4.6 for Linux」· C语言 代码 · 共 2,201 行 · 第 1/5 页

C
2,201
字号
		/* 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))	{		ereport(WARNING,				(errmsg("oldest xmin is far in the past"),				 errhint("Close open transactions soon to avoid wraparound problems.")));		limit = *oldestXmin;	}	*freezeLimit = limit;}/* *	vac_update_relstats() -- update statistics for one relation * *		Update the whole-relation statistics that are kept in its pg_class *		row.  There are additional stats that will be updated if we are *		doing ANALYZE, but we always update these stats.  This routine works *		for both index and heap relation entries in pg_class. * *		We violate no-overwrite semantics here by storing new values for the *		statistics columns directly into the pg_class tuple that's already on *		the page.  The reason for this is that if we updated these tuples in *		the usual way, vacuuming pg_class itself wouldn't work very well --- *		by the time we got done with a vacuum cycle, most of the tuples in *		pg_class would've been obsoleted.  Of course, this only works for *		fixed-size never-null columns, but these are. * *		This routine is shared by full VACUUM, lazy VACUUM, and stand-alone *		ANALYZE. */voidvac_update_relstats(Oid relid, BlockNumber num_pages, double num_tuples,					bool hasindex){	Relation	rd;	HeapTupleData rtup;	HeapTuple	ctup;	Form_pg_class pgcform;	Buffer		buffer;	/*	 * update number of tuples and number of pages in pg_class	 */	rd = heap_openr(RelationRelationName, RowExclusiveLock);	ctup = SearchSysCache(RELOID,						  ObjectIdGetDatum(relid),						  0, 0, 0);	if (!HeapTupleIsValid(ctup))		elog(ERROR, "pg_class entry for relid %u vanished during vacuuming",			 relid);	/* get the buffer cache tuple */	rtup.t_self = ctup->t_self;	ReleaseSysCache(ctup);	if (!heap_fetch(rd, SnapshotNow, &rtup, &buffer, false, NULL))		elog(ERROR, "pg_class entry for relid %u vanished during vacuuming",			 relid);	/* ensure no one else does this at the same time */	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);	/* overwrite the existing statistics in the tuple */	pgcform = (Form_pg_class) GETSTRUCT(&rtup);	pgcform->relpages = (int32) num_pages;	pgcform->reltuples = num_tuples;	pgcform->relhasindex = hasindex;	/*	 * If we have discovered that there are no indexes, then there's no	 * primary key either.	This could be done more thoroughly...	 */	if (!hasindex)		pgcform->relhaspkey = false;	LockBuffer(buffer, BUFFER_LOCK_UNLOCK);	/*	 * Invalidate the tuple in the catcaches; this also arranges to flush	 * the relation's relcache entry.  (If we fail to commit for some	 * reason, no flush will occur, but no great harm is done since there	 * are no noncritical state updates here.)	 */	CacheInvalidateHeapTuple(rd, &rtup);	/* Write the buffer */	WriteBuffer(buffer);	heap_close(rd, RowExclusiveLock);}/* *	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 voidvac_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, SnapshotNow, 1, entry);	tuple = heap_getnext(scan, ForwardScanDirection);	if (!HeapTupleIsValid(tuple))		elog(ERROR, "could not find tuple for database %u", dbid);	/* ensure no one else does this at the same time */	LockBuffer(scan->rs_cbuf, BUFFER_LOCK_EXCLUSIVE);	dbform = (Form_pg_database) GETSTRUCT(tuple);	/* overwrite the existing statistics in the tuple */	dbform->datvacuumxid = vacuumXID;	dbform->datfrozenxid = frozenXID;	LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);	/* invalidate the tuple in the cache and write the buffer */	CacheInvalidateHeapTuple(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 voidvac_truncate_clog(TransactionId vacuumXID, TransactionId frozenXID){	TransactionId myXID;	Relation	relation;	HeapScanDesc scan;	HeapTuple	tuple;	int32		age;	bool		vacuumAlreadyWrapped = false;	bool		frozenAlreadyWrapped = false;	myXID = GetCurrentTransactionId();	relation = heap_openr(DatabaseRelationName, AccessShareLock);	scan = heap_beginscan(relation, SnapshotNow, 0, NULL);	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)	{		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))		{			if (TransactionIdPrecedes(myXID, dbform->datvacuumxid))				vacuumAlreadyWrapped = true;			else if (TransactionIdPrecedes(dbform->datvacuumxid, vacuumXID))				vacuumXID = dbform->datvacuumxid;		}		if (TransactionIdIsNormal(dbform->datfrozenxid))		{			if (TransactionIdPrecedes(myXID, dbform->datfrozenxid))				frozenAlreadyWrapped = true;			else if (TransactionIdPrecedes(dbform->datfrozenxid, frozenXID))				frozenXID = dbform->datfrozenxid;		}	}	heap_endscan(scan);	heap_close(relation, AccessShareLock);	/*	 * Do not truncate CLOG if we seem to have suffered wraparound	 * already; the computed minimum XID might be bogus.	 */	if (vacuumAlreadyWrapped)	{		ereport(WARNING,				(errmsg("some databases have not been vacuumed in over 2 billion transactions"),				 errdetail("You may have already suffered transaction-wraparound data loss.")));		return;	}	/* Truncate CLOG to the oldest vacuumxid */	TruncateCLOG(vacuumXID);	/* Give warning about impending wraparound problems */	if (frozenAlreadyWrapped)	{		ereport(WARNING,				(errmsg("some databases have not been vacuumed in over 1 billion transactions"),				 errhint("Better vacuum them soon, or you may have a wraparound failure.")));	}	else	{		age = (int32) (myXID - frozenXID);		if (age > (int32) ((MaxTransactionId >> 3) * 3))			ereport(WARNING,					(errmsg("some databases have not been vacuumed in %d transactions",							age),					 errhint("Better vacuum them within %d transactions, "							 "or you may have a wraparound failure.",							 (int32) (MaxTransactionId >> 1) - age)));	}}/**************************************************************************** *																			* *			Code common to both flavors of VACUUM							* *																			* **************************************************************************** *//* *	vacuum_rel() -- vacuum one heap relation * *		Returns TRUE if we actually processed the relation (or can ignore it *		for some reason), FALSE if we failed to process it due to permissions *		or other reasons.  (A FALSE result really means that some data *		may have been left unvacuumed, so we can't update XID stats.) * *		Doing one heap at a time incurs extra overhead, since we need to *		check that the heap exists again just before we vacuum it.	The *		reason that we do this is so that vacuuming can be spread across *		many small transactions.  Otherwise, two-phase locking would require *		us to lock the entire database during one pass of the vacuum cleaner. * *		At entry and exit, we are not inside a transaction. */static boolvacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind){	LOCKMODE	lmode;	Relation	onerel;	LockRelId	onerelid;	Oid			toast_relid;	bool		result;	/* Begin a transaction for vacuuming this relation */	StartTransactionCommand();	SetQuerySnapshot();			/* might be needed for functions in								 * indexes */	/*	 * Check for user-requested abort.	Note we want this to be inside a	 * transaction, so xact.c doesn't issue useless WARNING.	 */	CHECK_FOR_INTERRUPTS();	/*	 * Race condition -- if the pg_class tuple has gone away since the	 * last time we saw it, we don't need to vacuum it.	 */	if (!SearchSysCacheExists(RELOID,							  ObjectIdGetDatum(relid),							  0, 0, 0))	{		CommitTransactionCommand();		return true;			/* okay 'cause no data there */	}	/*	 * Determine the type of lock we want --- hard exclusive lock for a	 * FULL vacuum, but just ShareUpdateExclusiveLock for concurrent	 * vacuum.	Either way, we can be sure that no other backend is	 * vacuuming the same table.	 */	lmode = vacstmt->full ? AccessExclusiveLock : ShareUpdateExclusiveLock;	/*	 * Open the class, get an appropriate lock on it, and check	 * permissions.	 *	 * We allow the user to vacuum a table if he is superuser, the table	 * owner, or the database owner (but in the latter case, only if it's	 * not a shared relation).	pg_class_ownercheck includes the superuser	 * case.	 *	 * Note we choose to treat permissions failure as a WARNING and keep	 * trying to vacuum the rest of the DB --- is this appropriate?	 */	onerel = relation_open(relid, lmode);	if (!(pg_class_ownercheck(RelationGetRelid(onerel), GetUserId()) ||		  (pg_database_ownercheck(MyDatabaseId, GetUserId()) && !onerel->rd_rel->relisshared)))	{		ereport(WARNING,				(errmsg("skipping \"%s\" --- only table or database owner can vacuum it",						RelationGetRelationName(onerel))));		relation_close(onerel, lmode);		CommitTransactionCommand();		return false;	}	/*	 * Check that it's a plain table; we used to do this in getrels() but	 * seems safer to check after we've locked the relation.	 */	if (onerel->rd_rel->relkind != expected_relkind)	{		ereport(WARNING,				(errmsg("skipping \"%s\" --- cannot vacuum indexes, views, or special system tables",						RelationGetRelationName(onerel))));		relation_close(onerel, lmode);		CommitTransactionCommand();		return false;	}	/*	 * Silently ignore tables that are temp tables of other backends ---	 * trying to vacuum these will lead to great unhappiness, since their	 * contents are probably not up-to-date on disk.  (We don't throw a	 * warning here; it would just lead to chatter during a database-wide	 * VACUUM.)	 */	if (isOtherTempNamespace(RelationGetNamespace(onerel)))	{		relation_close(onerel, lmode);		CommitTransactionCommand();		return true;			/* assume no long-lived data in temp								 * tables */	}	/*	 * Get a session-level lock too. This will protect our access to the	 * relation across multiple transactions, so that we can vacuum the	 * relation's TOAST table (if any) secure in the knowledge that no one	 * is deleting the parent relation.	 *	 * 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.	 */	onerelid = onerel->rd_lockInfo.lockRelId;	LockRelationForSession(&onerelid, lmode);	/*	 * Remember the relation's TOAST relation for later	 */	toast_relid = onerel->rd_rel->reltoastrelid;	/*	 * Do the actual work --- either FULL or "lazy" vacuum	 */	if (vacstmt->full)		full_vacuum_rel(onerel, vacstmt);	else		lazy_vacuum_rel(onerel, vacstmt);	result = true;				/* did the vacuum */	/* all done with this class, but hold lock until commit */	relation_close(onerel, NoLock);	/*	 * Complete the transaction and free all temporary memory used.	 */	CommitTransactionCommand();	/*	 * If the relation has a secondary toast rel, vacuum that too while we	 * still hold the session lock on the master table.  Note however that	 * "analyze" will not get done on the toast table.	This is good,	 * because the toaster always uses hardcoded index access and	 * statistics are totally unimportant for toast relations.	 */	if (toast_relid != InvalidOid)	{		if (!vacuum_rel(toast_relid, vacstmt, RELKIND_TOASTVALUE))			result = false;		/* failed to vacuum the TOAST table? */	}	/*	 * Now release the session-level lock on the master table.	 */

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?