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 + -
显示快捷键?