📄 vacuum.c
字号:
/*------------------------------------------------------------------------- * * vacuum.c * the postgres vacuum cleaner * * Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $Header: /usr/local/cvsroot/pgsql/src/backend/commands/vacuum.c,v 1.110.2.3 1999/08/25 12:01:45 ishii Exp $ * *------------------------------------------------------------------------- */#include <sys/types.h>#include <sys/file.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>#include "postgres.h"#include "access/genam.h"#include "access/heapam.h"#include "catalog/catalog.h"#include "catalog/catname.h"#include "catalog/index.h"#include "catalog/pg_operator.h"#include "catalog/pg_statistic.h"#include "catalog/pg_type.h"#include "commands/vacuum.h"#include "miscadmin.h"#include "parser/parse_oper.h"#include "storage/smgr.h"#include "utils/builtins.h"#include "utils/inval.h"#include "utils/portal.h"#include "utils/relcache.h"#include "utils/syscache.h"#ifndef HAVE_GETRUSAGE#include "rusagestub.h"#else#include <sys/time.h>#include <sys/resource.h>#endif /* #include <port-protos.h> *//* Why? */extern int BlowawayRelationBuffers(Relation rel, BlockNumber block);bool VacuumRunning = false;static Portal vc_portal;static int MESSAGE_LEVEL; /* message level */static TransactionId XmaxRecent;#define swapLong(a,b) {long tmp; tmp=a; a=b; b=tmp;}#define swapInt(a,b) {int tmp; tmp=a; a=b; b=tmp;}#define swapDatum(a,b) {Datum tmp; tmp=a; a=b; b=tmp;}#define VacAttrStatsEqValid(stats) ( stats->f_cmpeq.fn_addr != NULL )#define VacAttrStatsLtGtValid(stats) ( stats->f_cmplt.fn_addr != NULL && \ stats->f_cmpgt.fn_addr != NULL && \ RegProcedureIsValid(stats->outfunc) )/* non-export function prototypes */static void vc_init(void);static void vc_shutdown(void);static void vc_vacuum(NameData *VacRelP, bool analyze, List *va_cols);static VRelList vc_getrels(NameData *VacRelP);static void vc_vacone(Oid relid, bool analyze, List *va_cols);static void vc_scanheap(VRelStats *vacrelstats, Relation onerel, VPageList vacuum_pages, VPageList fraged_pages);static void vc_rpfheap(VRelStats *vacrelstats, Relation onerel, VPageList vacuum_pages, VPageList fraged_pages, int nindices, Relation *Irel);static void vc_vacheap(VRelStats *vacrelstats, Relation onerel, VPageList vpl);static void vc_vacpage(Page page, VPageDescr vpd);static void vc_vaconeind(VPageList vpl, Relation indrel, int num_tuples, int keep_tuples);static void vc_scanoneind(Relation indrel, int num_tuples);static void vc_attrstats(Relation onerel, VRelStats *vacrelstats, HeapTuple tuple);static void vc_bucketcpy(Form_pg_attribute attr, Datum value, Datum *bucket, int16 *bucket_len);static void vc_updstats(Oid relid, int num_pages, int num_tuples, bool hasindex, VRelStats *vacrelstats);static void vc_delhilowstats(Oid relid, int attcnt, int *attnums);static VPageDescr vc_tidreapped(ItemPointer itemptr, VPageList vpl);static void vc_reappage(VPageList vpl, VPageDescr vpc);static void vc_vpinsert(VPageList vpl, VPageDescr vpnew);static void vc_free(VRelList vrl);static void vc_getindices(Oid relid, int *nindices, Relation **Irel);static void vc_clsindices(int nindices, Relation *Irel);static void vc_mkindesc(Relation onerel, int nindices, Relation *Irel, IndDesc **Idesc);static void *vc_find_eq(void *bot, int nelem, int size, void *elm, int (*compar) (const void *, const void *));static int vc_cmp_blk(const void *left, const void *right);static int vc_cmp_offno(const void *left, const void *right);static int vc_cmp_vtlinks(const void *left, const void *right);static bool vc_enough_space(VPageDescr vpd, Size len);voidvacuum(char *vacrel, bool verbose, bool analyze, List *va_spec){ char *pname; MemoryContext old; PortalVariableMemory pmem; NameData VacRel; List *le; List *va_cols = NIL; /* * Create a portal for safe memory across transctions. We need to * palloc the name space for it because our hash function expects the * name to be on a longword boundary. CreatePortal copies the name to * safe storage for us. */ pname = (char *) palloc(strlen(VACPNAME) + 1); strcpy(pname, VACPNAME); vc_portal = CreatePortal(pname); pfree(pname); if (verbose) MESSAGE_LEVEL = NOTICE; else MESSAGE_LEVEL = DEBUG; /* vacrel gets de-allocated on transaction commit */ if (vacrel) strcpy(VacRel.data, vacrel); pmem = PortalGetVariableMemory(vc_portal); old = MemoryContextSwitchTo((MemoryContext) pmem); if (va_spec != NIL && !analyze) elog(ERROR, "Can't vacuum columns, only tables. You can 'vacuum analyze' columns."); foreach(le, va_spec) { char *col = (char *) lfirst(le); char *dest; dest = (char *) palloc(strlen(col) + 1); strcpy(dest, col); va_cols = lappend(va_cols, dest); } MemoryContextSwitchTo(old); /* initialize vacuum cleaner */ vc_init(); /* vacuum the database */ if (vacrel) vc_vacuum(&VacRel, analyze, va_cols); else vc_vacuum(NULL, analyze, NIL); PortalDestroy(&vc_portal); /* clean up */ vc_shutdown();}/* * vc_init(), vc_shutdown() -- start up and shut down the vacuum cleaner. * * We run exactly one vacuum cleaner at a time. We use the file system * to guarantee an exclusive lock on vacuuming, since a single vacuum * cleaner instantiation crosses transaction boundaries, and we'd lose * postgres-style locks at the end of every transaction. * * The strangeness with committing and starting transactions in the * init and shutdown routines is due to the fact that the vacuum cleaner * is invoked via a sql command, and so is already executing inside * a transaction. We need to leave ourselves in a predictable state * on entry and exit to the vacuum cleaner. We commit the transaction * started in PostgresMain() inside vc_init(), and start one in * vc_shutdown() to match the commit waiting for us back in * PostgresMain(). */static voidvc_init(){ int fd;#ifndef __CYGWIN32__ if ((fd = open("pg_vlock", O_CREAT | O_EXCL, 0600)) < 0)#else if ((fd = open("pg_vlock", O_CREAT | O_EXCL | O_BINARY, 0600)) < 0)#endif { elog(ERROR, "Can't create lock file. Is another vacuum cleaner running?\n\\tIf not, you may remove the pg_vlock file in the %s\n\\tdirectory", DatabasePath); } close(fd); /* * By here, exclusive open on the lock file succeeded. If we abort * for any reason during vacuuming, we need to remove the lock file. * This global variable is checked in the transaction manager on xact * abort, and the routine vc_abort() is called if necessary. */ VacuumRunning = true; /* matches the StartTransaction in PostgresMain() */ CommitTransactionCommand();}static voidvc_shutdown(){ /* on entry, we are not in a transaction */ /* * 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... */ unlink(RELCACHE_INIT_FILENAME); /* remove the vacuum cleaner lock file */ if (unlink("pg_vlock") < 0) elog(ERROR, "vacuum: can't destroy lock file!"); /* okay, we're done */ VacuumRunning = false; /* matches the CommitTransaction in PostgresMain() */ StartTransactionCommand();}voidvc_abort(){ /* on abort, remove the vacuum cleaner lock file */ unlink("pg_vlock"); VacuumRunning = false;}/* * vc_vacuum() -- vacuum the database. * * This routine builds a list of relations to vacuum, and then calls * code that vacuums them one at a time. We are careful to vacuum each * relation in a separate transaction in order to avoid holding too many * locks at one time. */static voidvc_vacuum(NameData *VacRelP, bool analyze, List *va_cols){ VRelList vrl, cur; /* get list of relations */ vrl = vc_getrels(VacRelP); if (analyze && VacRelP == NULL && vrl != NULL) vc_delhilowstats(InvalidOid, 0, NULL); /* vacuum each heap relation */ for (cur = vrl; cur != (VRelList) NULL; cur = cur->vrl_next) vc_vacone(cur->vrl_relid, analyze, va_cols); vc_free(vrl);}static VRelListvc_getrels(NameData *VacRelP){ Relation rel; TupleDesc tupdesc; HeapScanDesc scan; HeapTuple tuple; PortalVariableMemory portalmem; MemoryContext old; VRelList vrl, cur; Datum d; char *rname; char rkind; bool n; bool found = false; ScanKeyData key; StartTransactionCommand(); if (VacRelP->data) { ScanKeyEntryInitialize(&key, 0x0, Anum_pg_class_relname, F_NAMEEQ, PointerGetDatum(VacRelP->data)); } else { ScanKeyEntryInitialize(&key, 0x0, Anum_pg_class_relkind, F_CHAREQ, CharGetDatum('r')); } portalmem = PortalGetVariableMemory(vc_portal); vrl = cur = (VRelList) NULL; rel = heap_openr(RelationRelationName); tupdesc = RelationGetDescr(rel); scan = heap_beginscan(rel, false, SnapshotNow, 1, &key); while (HeapTupleIsValid(tuple = heap_getnext(scan, 0))) { found = true; d = heap_getattr(tuple, Anum_pg_class_relname, tupdesc, &n); rname = (char *) d; d = heap_getattr(tuple, Anum_pg_class_relkind, tupdesc, &n); rkind = DatumGetChar(d); if (rkind != RELKIND_RELATION) { elog(NOTICE, "Vacuum: can not process index and certain system tables"); continue; } /* get a relation list entry for this guy */ old = MemoryContextSwitchTo((MemoryContext) portalmem); if (vrl == (VRelList) NULL) vrl = cur = (VRelList) palloc(sizeof(VRelListData)); else { cur->vrl_next = (VRelList) palloc(sizeof(VRelListData)); cur = cur->vrl_next; } MemoryContextSwitchTo(old); cur->vrl_relid = tuple->t_data->t_oid; cur->vrl_next = (VRelList) NULL; } if (found == false) elog(NOTICE, "Vacuum: table not found"); heap_endscan(scan); heap_close(rel); CommitTransactionCommand(); return vrl;}/* * vc_vacone() -- vacuum one heap relation * * This routine vacuums a single heap, cleans out its indices, and * updates its statistics num_pages and num_tuples statistics. * * 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. */static voidvc_vacone(Oid relid, bool analyze, List *va_cols){ HeapTuple tuple, typetuple; Relation onerel; VPageListData vacuum_pages; /* List of pages to vacuum and/or clean * indices */ VPageListData fraged_pages; /* List of pages with space enough for * re-using */ VPageDescr *vpp; Relation *Irel; int32 nindices, i; VRelStats *vacrelstats; StartTransactionCommand(); /* * Race condition -- if the pg_class tuple has gone away since the * last time we saw it, we don't need to vacuum it. */ tuple = SearchSysCacheTuple(RELOID, ObjectIdGetDatum(relid), 0, 0, 0); if (!HeapTupleIsValid(tuple)) { CommitTransactionCommand(); return; } /* now open the class and vacuum it */ onerel = heap_open(relid); vacrelstats = (VRelStats *) palloc(sizeof(VRelStats)); vacrelstats->relid = relid; vacrelstats->num_pages = vacrelstats->num_tuples = 0; vacrelstats->hasindex = false; if (analyze && !IsSystemRelationName((RelationGetRelationName(onerel))->data)) { int attr_cnt, *attnums = NULL; Form_pg_attribute *attr; attr_cnt = onerel->rd_att->natts; attr = onerel->rd_att->attrs; if (va_cols != NIL) { int tcnt = 0; List *le; if (length(va_cols) > attr_cnt) elog(ERROR, "vacuum: too many attributes specified for relation %s", (RelationGetRelationName(onerel))->data); attnums = (int *) palloc(attr_cnt * sizeof(int)); foreach(le, va_cols) { char *col = (char *) lfirst(le); for (i = 0; i < attr_cnt; i++) { if (namestrcmp(&(attr[i]->attname), col) == 0) break; } if (i < attr_cnt) /* found */ attnums[tcnt++] = i; else { elog(ERROR, "vacuum: there is no attribute %s in %s", col, (RelationGetRelationName(onerel))->data); } } attr_cnt = tcnt; } vacrelstats->vacattrstats = (VacAttrStats *) palloc(attr_cnt * sizeof(VacAttrStats)); for (i = 0; i < attr_cnt; i++) { Operator func_operator; Form_pg_operator pgopform; VacAttrStats *stats; stats = &vacrelstats->vacattrstats[i]; stats->attr = palloc(ATTRIBUTE_TUPLE_SIZE); memmove(stats->attr, attr[((attnums) ? attnums[i] : i)], ATTRIBUTE_TUPLE_SIZE); stats->best = stats->guess1 = stats->guess2 = 0; stats->max = stats->min = 0; stats->best_len = stats->guess1_len = stats->guess2_len = 0; stats->max_len = stats->min_len = 0; stats->initialized = false; stats->best_cnt = stats->guess1_cnt = stats->guess1_hits = stats->guess2_hits = 0; stats->max_cnt = stats->min_cnt = stats->null_cnt = stats->nonnull_cnt = 0;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -