📄 vacuum.c
字号:
* defenses in GetNewTransactionId, but we keep them anyway. */ relation = heap_open(DatabaseRelationId, 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; namecpy(&oldest_datname, &dbform->datname); } } } 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); /* * Do not update varsup.c if we seem to have suffered wraparound already; * the computed XID might be bogus. */ 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."))); return; } /* Update the wrap limit for GetNewTransactionId */ SetTransactionIdLimit(frozenXID, &oldest_datname); /* Give warning about impending wraparound problems */ age = (int32) (myXID - frozenXID); if (age > (int32) ((MaxTransactionId >> 3) * 3)) ereport(WARNING, (errmsg("database \"%s\" must be vacuumed within %u transactions", NameStr(oldest_datname), (MaxTransactionId >> 1) - age), errhint("To avoid a database shutdown, execute a full-database VACUUM in \"%s\".", NameStr(oldest_datname))));}/**************************************************************************** * * * 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(); /* functions in indexes may want a snapshot set */ ActiveSnapshot = CopySnapshot(GetTransactionSnapshot()); /* * Tell the cache replacement strategy that vacuum is causing all * following IO */ StrategyHintVacuum(true); /* * 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)) { StrategyHintVacuum(false); 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); StrategyHintVacuum(false); CommitTransactionCommand(); return false; } /* * Check that it's a plain table; we used to do this in get_rel_oids() 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); StrategyHintVacuum(false); 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); StrategyHintVacuum(false); 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, onerel->rd_istemp, 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. */ StrategyHintVacuum(false); 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. */ UnlockRelationForSession(&onerelid, lmode); return result;}/**************************************************************************** * * * Code for VACUUM FULL (only) * * * **************************************************************************** *//* * full_vacuum_rel() -- perform FULL VACUUM for one heap relation * * This routine vacuums a single heap, cleans out its indexes, and * updates its num_pages and num_tuples statistics. * * At entry, we have already established a transaction and opened * and locked the relation. */static voidfull_vacuum_rel(Relation onerel, VacuumStmt *vacstmt){ VacPageListData vacuum_pages; /* List of pages to vacuum and/or * clean indexes */ VacPageListData fraged_pages; /* List of pages with space enough for * re-using */ Relation *Irel; int nindexes, i; VRelStats *vacrelstats; vacuum_set_xid_limits(vacstmt, onerel->rd_rel->relisshared, &OldestXmin, &FreezeLimit); /* * Set up statistics-gathering machinery. */ vacrelstats = (VRelStats *) palloc(sizeof(VRelStats)); vacrelstats->rel_pages = 0; vacrelstats->rel_tuples = 0; vacrelstats->hasindex = false; /* scan the heap */ vacuum_pages.num_pages = fraged_pages.num_pages = 0; scan_heap(vacrelstats, onerel, &vacuum_pages, &fraged_pages); /* Now open all indexes of the relation */ vac_open_indexes(onerel, AccessExclusiveLock, &nindexes, &Irel); if (nindexes > 0) vacrelstats->hasindex = true; /* Clean/scan index relation(s) */ if (Irel != NULL) { if (vacuum_pages.num_pages > 0) { for (i = 0; i < nindexes; i++) vacuum_index(&vacuum_pages, Irel[i], vacrelstats->rel_tuples, 0); } else { /* just scan indexes to update statistic */ for (i = 0; i < nindexes; i++) scan_index(Irel[i], vacrelstats->rel_tuples); } } if (fraged_pages.num_pages > 0) { /* Try to shrink heap */ repair_frag(vacrelstats, onerel, &vacuum_pages, &fraged_pages, nindexes, Irel); vac_close_indexes(nindexes, Irel, NoLock); } else { vac_close_indexes(nindexes, Irel, NoLock); if (vacuum_pages.num_pages > 0) { /* Clean pages from vacuum_pages list */ vacuum_heap(vacrelstats, onerel, &vacuum_pages); } } /* update shared free space map with final free space info */ vac_update_fsm(onerel, &fraged_pages, vacrelstats->rel_pages); /* update statistics in pg_class */ vac_update_relstats(RelationGetRelid(onerel), vacrelstats->rel_pages, vacrelstats->rel_tuples, vacrelstats->hasindex); /* report results to the stats collector, too */ pgstat_report_vacuum(RelationGetRelid(onerel), onerel->rd_rel->relisshared, vacstmt->analyze, vacrelstats->rel_tuples);}/* * scan_heap() -- scan an open heap relation * * This routine sets commit status bits, constructs vacuum_pages (list * of pages we need to compact free space on and/or clean indexes of * deleted tuples), constructs fraged_pages (list of pages with free * space that tuples could be moved into), and calculates statistics * on the number of live tuples in the heap. */static voidscan_heap(VRelStats *vacrelstats, Relation onerel, VacPageList vacuum_pages, VacPageList fraged_pages){ BlockNumber nblocks, blkno; HeapTupleData tuple; char *relname; VacPage vacpage; BlockNumber empty_pages, empty_end_pages; double num_tuples, tups_vacuumed, nkeep, nunused; double free_space, usable_free_space; Size min_tlen = MaxTupleSize; Size max_tlen = 0; bool do_shrinking = true; VTupleLink vtlinks = (VTupleLink) palloc(100 * sizeof(VTupleLinkData)); int num_vtlinks = 0; int free_vtlinks = 100; PGRUsage ru0; pg_rusage_init(&ru0); relname = RelationGetRelationName(onerel); ereport(elevel, (errmsg("vacuuming \"%s.%s\"", get_namespace_name(RelationGetNamespace(onerel)), relname))); empty_pages = empty_end_pages = 0; num_tuples = tups_vacuumed = nkeep = nunused = 0; free_space = 0; nblocks = RelationGetNumberOfBlocks(onerel); /* * We initially create each VacPage item in a maximal-sized workspace, * then copy the workspace into a just-large-enough copy. */ vacpage = (VacPage) palloc(sizeof(VacPageData) + MaxOffsetNumber * sizeof(OffsetNumber)); for (blkno = 0; blkno < nblocks; blkno++) { Page page, tempPage = NULL; bool do_reap, do_frag; Buffer buf; OffsetNumber offnum, maxoff; bool pgchanged, notup;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -