📄 vacuum.c
字号:
/*------------------------------------------------------------------------- * * vacuum.c * The postgres vacuum cleaner. * * This file includes the "full" version of VACUUM, as well as control code * used by all three of full VACUUM, lazy VACUUM, and ANALYZE. See * vacuumlazy.c and analyze.c for the rest of the code for the latter two. * * * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.317.2.3 2006/01/18 20:35:15 tgl Exp $ * *------------------------------------------------------------------------- */#include "postgres.h"#include <sys/time.h>#include <unistd.h>#include "access/clog.h"#include "access/genam.h"#include "access/heapam.h"#include "access/subtrans.h"#include "access/xlog.h"#include "catalog/catalog.h"#include "catalog/namespace.h"#include "catalog/pg_database.h"#include "catalog/pg_index.h"#include "commands/dbcommands.h"#include "commands/vacuum.h"#include "executor/executor.h"#include "miscadmin.h"#include "postmaster/autovacuum.h"#include "storage/freespace.h"#include "storage/procarray.h"#include "storage/smgr.h"#include "tcop/pquery.h"#include "utils/acl.h"#include "utils/builtins.h"#include "utils/flatfiles.h"#include "utils/fmgroids.h"#include "utils/inval.h"#include "utils/lsyscache.h"#include "utils/memutils.h"#include "utils/pg_rusage.h"#include "utils/relcache.h"#include "utils/syscache.h"#include "pgstat.h"/* * VacPage structures keep track of each page on which we find useful * amounts of free space. */typedef struct VacPageData{ BlockNumber blkno; /* BlockNumber of this Page */ Size free; /* FreeSpace on this Page */ uint16 offsets_used; /* Number of OffNums used by vacuum */ uint16 offsets_free; /* Number of OffNums free or to be free */ OffsetNumber offsets[1]; /* Array of free OffNums */} VacPageData;typedef VacPageData *VacPage;typedef struct VacPageListData{ BlockNumber empty_end_pages; /* Number of "empty" end-pages */ int num_pages; /* Number of pages in pagedesc */ int num_allocated_pages; /* Number of allocated pages in * pagedesc */ VacPage *pagedesc; /* Descriptions of pages */} VacPageListData;typedef VacPageListData *VacPageList;/* * The "vtlinks" array keeps information about each recently-updated tuple * ("recent" meaning its XMAX is too new to let us recycle the tuple). * We store the tuple's own TID as well as its t_ctid (its link to the next * newer tuple version). Searching in this array allows us to follow update * chains backwards from newer to older tuples. When we move a member of an * update chain, we must move *all* the live members of the chain, so that we * can maintain their t_ctid link relationships (we must not just overwrite * t_ctid in an existing tuple). * * Note: because t_ctid links can be stale (this would only occur if a prior * VACUUM crashed partway through), it is possible that new_tid points to an * empty slot or unrelated tuple. We have to check the linkage as we follow * it, just as is done in EvalPlanQual. */typedef struct VTupleLinkData{ ItemPointerData new_tid; /* t_ctid of an updated tuple */ ItemPointerData this_tid; /* t_self of the tuple */} VTupleLinkData;typedef VTupleLinkData *VTupleLink;/* * We use an array of VTupleMoveData to plan a chain tuple move fully * before we do it. */typedef struct VTupleMoveData{ ItemPointerData tid; /* tuple ID */ VacPage vacpage; /* where to move it to */ bool cleanVpd; /* clean vacpage before using? */} VTupleMoveData;typedef VTupleMoveData *VTupleMove;/* * VRelStats contains the data acquired by scan_heap for use later */typedef struct VRelStats{ /* miscellaneous statistics */ BlockNumber rel_pages; double rel_tuples; Size min_tlen; Size max_tlen; bool hasindex; /* vtlinks array for tuple chain following - sorted by new_tid */ int num_vtlinks; VTupleLink vtlinks;} VRelStats;/*---------------------------------------------------------------------- * ExecContext: * * As these variables always appear together, we put them into one struct * and pull initialization and cleanup into separate routines. * ExecContext is used by repair_frag() and move_xxx_tuple(). More * accurately: It is *used* only in move_xxx_tuple(), but because this * routine is called many times, we initialize the struct just once in * repair_frag() and pass it on to move_xxx_tuple(). */typedef struct ExecContextData{ ResultRelInfo *resultRelInfo; EState *estate; TupleTableSlot *slot;} ExecContextData;typedef ExecContextData *ExecContext;static voidExecContext_Init(ExecContext ec, Relation rel){ TupleDesc tupdesc = RelationGetDescr(rel); /* * We need a ResultRelInfo and an EState so we can use the regular * executor's index-entry-making machinery. */ ec->estate = CreateExecutorState(); ec->resultRelInfo = makeNode(ResultRelInfo); ec->resultRelInfo->ri_RangeTableIndex = 1; /* dummy */ ec->resultRelInfo->ri_RelationDesc = rel; ec->resultRelInfo->ri_TrigDesc = NULL; /* we don't fire triggers */ ExecOpenIndices(ec->resultRelInfo); ec->estate->es_result_relations = ec->resultRelInfo; ec->estate->es_num_result_relations = 1; ec->estate->es_result_relation_info = ec->resultRelInfo; /* Set up a tuple slot too */ ec->slot = MakeSingleTupleTableSlot(tupdesc);}static voidExecContext_Finish(ExecContext ec){ ExecDropSingleTupleTableSlot(ec->slot); ExecCloseIndices(ec->resultRelInfo); FreeExecutorState(ec->estate);}/* * End of ExecContext Implementation *---------------------------------------------------------------------- */static MemoryContext vac_context = NULL;static int elevel = -1;static TransactionId OldestXmin;static TransactionId FreezeLimit;/* non-export function prototypes */static List *get_rel_oids(List *relids, const RangeVar *vacrel, const char *stmttype);static void vac_update_dbstats(Oid dbid, TransactionId vacuumXID, TransactionId frozenXID);static void vac_truncate_clog(TransactionId vacuumXID, TransactionId frozenXID);static bool vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind);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, VacPageList vacuum_pages, VacPageList fraged_pages, int nindexes, Relation *Irel);static void move_chain_tuple(Relation rel, Buffer old_buf, Page old_page, HeapTuple old_tup, Buffer dst_buf, Page dst_page, VacPage dst_vacpage, ExecContext ec, ItemPointer ctid, bool cleanVpd);static void move_plain_tuple(Relation rel, Buffer old_buf, Page old_page, HeapTuple old_tup, Buffer dst_buf, Page dst_page, VacPage dst_vacpage, ExecContext ec);static void update_hint_bits(Relation rel, VacPageList fraged_pages, int num_fraged_pages, BlockNumber last_move_dest_block, int num_moved);static void vacuum_heap(VRelStats *vacrelstats, Relation onerel, VacPageList vacpagelist);static void vacuum_page(Relation onerel, Buffer buffer, VacPage vacpage);static void vacuum_index(VacPageList vacpagelist, Relation indrel, double num_tuples, int keep_tuples);static void scan_index(Relation indrel, double num_tuples);static bool tid_reaped(ItemPointer itemptr, void *state);static bool dummy_tid_reaped(ItemPointer itemptr, void *state);static void vac_update_fsm(Relation onerel, VacPageList fraged_pages, BlockNumber rel_pages);static VacPage copy_vac_page(VacPage vacpage);static void vpage_insert(VacPageList vacpagelist, VacPage vpnew);static void *vac_bsearch(const void *key, const void *base, size_t nelem, size_t size, int (*compar) (const void *, const void *));static int vac_cmp_blk(const void *left, const void *right);static int vac_cmp_offno(const void *left, const void *right);static int vac_cmp_vtlinks(const void *left, const void *right);static bool enough_space(VacPage vacpage, Size len);/**************************************************************************** * * * Code common to all flavors of VACUUM and ANALYZE * * * **************************************************************************** *//* * Primary entry point for VACUUM and ANALYZE commands. * * relids is normally NIL; if it is not, then it provides the list of * relation OIDs to be processed, and vacstmt->relation is ignored. * (The non-NIL case is currently only used by autovacuum.) * * It is the caller's responsibility that both vacstmt and relids * (if given) be allocated in a memory context that won't disappear * at transaction commit. In fact this context must be QueryContext * to avoid complaints from PreventTransactionChain. */voidvacuum(VacuumStmt *vacstmt, List *relids){ const char *stmttype = vacstmt->vacuum ? "VACUUM" : "ANALYZE"; TransactionId initialOldestXmin = InvalidTransactionId; TransactionId initialFreezeLimit = InvalidTransactionId; volatile MemoryContext anl_context = NULL; volatile bool all_rels, in_outer_xact, use_own_xacts; List *relations; if (vacstmt->verbose) elevel = INFO; else elevel = DEBUG2; /* * We cannot run VACUUM inside a user transaction block; if we were inside * a transaction, then our commit- and start-transaction-command calls * would not have the intended effect! Furthermore, the forced commit that * occurs before truncating the relation's file would have the effect of * committing the rest of the user's transaction too, which would * certainly not be the desired behavior. (This only applies to VACUUM * FULL, though. We could in theory run lazy VACUUM inside a transaction * block, but we choose to disallow that case because we'd rather commit * as soon as possible after finishing the vacuum. This is mainly so that * we can let go the AccessExclusiveLock that we may be holding.) * * ANALYZE (without VACUUM) can run either way. */ if (vacstmt->vacuum) { PreventTransactionChain((void *) vacstmt, stmttype); in_outer_xact = false; } else in_outer_xact = IsInTransactionChain((void *) vacstmt); /* * Disallow the combination VACUUM FULL FREEZE; although it would mostly * work, VACUUM FULL's ability to move tuples around means that it is * injecting its own XID into tuple visibility checks. We'd have to * guarantee that every moved tuple is properly marked XMIN_COMMITTED or * XMIN_INVALID before the end of the operation. There are corner cases * where this does not happen, and getting rid of them all seems hard (not * to mention fragile to maintain). On the whole it's not worth it * compared to telling people to use two operations. See pgsql-hackers * discussion of 27-Nov-2004, and comments below for update_hint_bits(). * * Note: this is enforced here, and not in the grammar, since (a) we can * give a better error message, and (b) we might want to allow it again * someday. */ if (vacstmt->vacuum && vacstmt->full && vacstmt->freeze) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("VACUUM FULL FREEZE is not supported"), errhint("Use VACUUM FULL, then VACUUM FREEZE."))); /* * Send info about dead objects to the statistics collector, unless * we are in autovacuum --- autovacuum.c does this for itself. */ if (vacstmt->vacuum && !IsAutoVacuumProcess()) pgstat_vacuum_tabstat(); /* * Create special memory context for cross-transaction storage. * * Since it is a child of PortalContext, it will go away eventually even * if we suffer an error; there's no need for special abort cleanup logic. */ vac_context = AllocSetContextCreate(PortalContext, "Vacuum", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); /* Remember whether we are processing everything in the DB */ all_rels = (relids == NIL && vacstmt->relation == NULL); /* * Build list of relations to process, unless caller gave us one. (If we * build one, we put it in vac_context for safekeeping.) */ relations = get_rel_oids(relids, vacstmt->relation, stmttype); if (vacstmt->vacuum && all_rels) { /* * It's a database-wide VACUUM. * * 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); } /* * Decide whether we need to start/commit our own transactions. * * For VACUUM (with or without ANALYZE): always do so, so that we can * release locks as soon as possible. (We could possibly use the outer * transaction for a one-table VACUUM, but handling TOAST tables would be * problematic.) * * For ANALYZE (no VACUUM): if inside a transaction block, we cannot * start/commit our own transactions. Also, there's no need to do so if * only processing one relation. For multiple relations when not within a * transaction block, use own transactions so we can release locks sooner. */ if (vacstmt->vacuum) use_own_xacts = true; else { Assert(vacstmt->analyze); if (in_outer_xact) use_own_xacts = false; else if (list_length(relations) > 1) use_own_xacts = true; else use_own_xacts = false; } /* * If we are running ANALYZE without per-table transactions, we'll need a * memory context with table lifetime. */ if (!use_own_xacts) anl_context = AllocSetContextCreate(PortalContext, "Analyze", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); /* * vacuum_rel expects to be entered with no transaction active; it will * start and commit its own transaction. But we are called by an SQL * command, and so we are executing inside a transaction already. We * commit the transaction started in PostgresMain() here, and start * another one before exiting to match the commit waiting for us back in * PostgresMain().
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -