📄 autovacuum.c
字号:
if (!HeapTupleIsValid(tup)) elog(ERROR, "could not find tuple for database %u", MyDatabaseId); dbForm = (Form_pg_database) GETSTRUCT(tup); if (!dbForm->datallowconn || dbForm->datistemplate) freeze = true; else freeze = false; systable_endscan(scan); heap_close(dbRel, AccessShareLock); elog(DEBUG2, "autovacuum: VACUUM%s whole database", (freeze) ? " FREEZE" : ""); autovacuum_do_vac_analyze(NIL, true, false, freeze); /* Finally close out the last transaction. */ CommitTransactionCommand();}/* * Process a database table-by-table * * dbentry must be a valid pointer to the database entry in the stats * databases' hash table, and it will be used to determine whether vacuum or * analyze is needed on a per-table basis. * * Note that CHECK_FOR_INTERRUPTS is supposed to be used in certain spots in * order not to ignore shutdown commands for too long. */static voiddo_autovacuum(PgStat_StatDBEntry *dbentry){ Relation classRel, avRel; HeapTuple tuple; HeapScanDesc relScan; List *vacuum_tables = NIL; List *toast_table_ids = NIL; ListCell *cell; PgStat_StatDBEntry *shared; /* Start a transaction so our commands have one to play into. */ StartTransactionCommand(); /* functions in indexes may want a snapshot set */ ActiveSnapshot = CopySnapshot(GetTransactionSnapshot()); /* * Clean up any dead statistics collector entries for this DB. * We always want to do this exactly once per DB-processing cycle, * even if we find nothing worth vacuuming in the database. */ pgstat_vacuum_tabstat(); /* * StartTransactionCommand and CommitTransactionCommand will automatically * switch to other contexts. We need this one to keep the list of * relations to vacuum/analyze across transactions. */ MemoryContextSwitchTo(AutovacMemCxt); /* The database hash where pgstat keeps shared relations */ shared = pgstat_fetch_stat_dbentry(InvalidOid); classRel = heap_open(RelationRelationId, AccessShareLock); avRel = heap_open(AutovacuumRelationId, AccessShareLock); /* * Scan pg_class and determine which tables to vacuum. * * The stats subsystem collects stats for toast tables independently of * the stats for their parent tables. We need to check those stats since * in cases with short, wide tables there might be proportionally much * more activity in the toast table than in its parent. * * Since we can only issue VACUUM against the parent table, we need to * transpose a decision to vacuum a toast table into a decision to vacuum * its parent. There's no point in considering ANALYZE on a toast table, * either. To support this, we keep a list of OIDs of toast tables that * need vacuuming alongside the list of regular tables. Regular tables * will be entered into the table list even if they appear not to need * vacuuming; we go back and re-mark them after finding all the vacuumable * toast tables. */ relScan = heap_beginscan(classRel, SnapshotNow, 0, NULL); while ((tuple = heap_getnext(relScan, ForwardScanDirection)) != NULL) { Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple); Form_pg_autovacuum avForm = NULL; PgStat_StatTabEntry *tabentry; SysScanDesc avScan; HeapTuple avTup; ScanKeyData entry[1]; Oid relid; /* Consider only regular and toast tables. */ if (classForm->relkind != RELKIND_RELATION && classForm->relkind != RELKIND_TOASTVALUE) continue; /* * Skip temp tables (i.e. those in temp namespaces). We cannot safely * process other backends' temp tables. */ if (isAnyTempNamespace(classForm->relnamespace)) continue; relid = HeapTupleGetOid(tuple); /* See if we have a pg_autovacuum entry for this relation. */ ScanKeyInit(&entry[0], Anum_pg_autovacuum_vacrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(relid)); avScan = systable_beginscan(avRel, AutovacuumRelidIndexId, true, SnapshotNow, 1, entry); avTup = systable_getnext(avScan); if (HeapTupleIsValid(avTup)) avForm = (Form_pg_autovacuum) GETSTRUCT(avTup); if (classForm->relisshared && PointerIsValid(shared)) tabentry = hash_search(shared->tables, &relid, HASH_FIND, NULL); else tabentry = hash_search(dbentry->tables, &relid, HASH_FIND, NULL); test_rel_for_autovac(relid, tabentry, classForm, avForm, &vacuum_tables, &toast_table_ids); systable_endscan(avScan); } heap_endscan(relScan); heap_close(avRel, AccessShareLock); heap_close(classRel, AccessShareLock); /* * Perform operations on collected tables. */ foreach(cell, vacuum_tables) { autovac_table *tab = lfirst(cell); CHECK_FOR_INTERRUPTS(); /* * Check to see if we need to force vacuuming of this table because * its toast table needs it. */ if (OidIsValid(tab->toastrelid) && !tab->dovacuum && list_member_oid(toast_table_ids, tab->toastrelid)) { tab->dovacuum = true; elog(DEBUG2, "autovac: VACUUM %u because of TOAST table", tab->relid); } /* Otherwise, ignore table if it needs no work */ if (!tab->dovacuum && !tab->doanalyze) continue; /* Set the vacuum cost parameters for this table */ VacuumCostDelay = tab->vacuum_cost_delay; VacuumCostLimit = tab->vacuum_cost_limit; autovacuum_do_vac_analyze(list_make1_oid(tab->relid), tab->dovacuum, tab->doanalyze, false); } /* Finally close out the last transaction. */ CommitTransactionCommand();}/* * test_rel_for_autovac * * Check whether a table needs to be vacuumed or analyzed. Add it to the * appropriate output list if so. * * A table needs to be vacuumed if the number of dead tuples exceeds a * threshold. This threshold is calculated as * * threshold = vac_base_thresh + vac_scale_factor * reltuples * * For analyze, the analysis done is that the number of tuples inserted, * deleted and updated since the last analyze exceeds a threshold calculated * in the same fashion as above. Note that the collector actually stores * the number of tuples (both live and dead) that there were as of the last * analyze. This is asymmetric to the VACUUM case. * * A table whose pg_autovacuum.enabled value is false, is automatically * skipped. Thus autovacuum can be disabled for specific tables. Also, * when the stats collector does not have data about a table, it will be * skipped. * * A table whose vac_base_thresh value is <0 takes the base value from the * autovacuum_vacuum_threshold GUC variable. Similarly, a vac_scale_factor * value <0 is substituted with the value of * autovacuum_vacuum_scale_factor GUC variable. Ditto for analyze. */static voidtest_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry, Form_pg_class classForm, Form_pg_autovacuum avForm, List **vacuum_tables, List **toast_table_ids){ Relation rel; float4 reltuples; /* pg_class.reltuples */ /* constants from pg_autovacuum or GUC variables */ int vac_base_thresh, anl_base_thresh; float4 vac_scale_factor, anl_scale_factor; /* thresholds calculated from above constants */ float4 vacthresh, anlthresh; /* number of vacuum (resp. analyze) tuples at this time */ float4 vactuples, anltuples; /* cost-based vacuum delay parameters */ int vac_cost_limit; int vac_cost_delay; bool dovacuum; bool doanalyze; /* User disabled it in pg_autovacuum? */ if (avForm && !avForm->enabled) return; /* * Skip a table not found in stat hash. If it's not acted upon, there's * no need to vacuum it. (Note that database-level check will take care * of Xid wraparound.) */ if (!PointerIsValid(tabentry)) return; rel = RelationIdGetRelation(relid); /* The table was recently dropped? */ if (!PointerIsValid(rel)) return; reltuples = rel->rd_rel->reltuples; vactuples = tabentry->n_dead_tuples; anltuples = tabentry->n_live_tuples + tabentry->n_dead_tuples - tabentry->last_anl_tuples; /* * If there is a tuple in pg_autovacuum, use it; else, use the GUC * defaults. Note that the fields may contain "-1" (or indeed any * negative value), which means use the GUC defaults for each setting. */ if (avForm != NULL) { vac_scale_factor = (avForm->vac_scale_factor >= 0) ? avForm->vac_scale_factor : autovacuum_vac_scale; vac_base_thresh = (avForm->vac_base_thresh >= 0) ? avForm->vac_base_thresh : autovacuum_vac_thresh; anl_scale_factor = (avForm->anl_scale_factor >= 0) ? avForm->anl_scale_factor : autovacuum_anl_scale; anl_base_thresh = (avForm->anl_base_thresh >= 0) ? avForm->anl_base_thresh : autovacuum_anl_thresh; vac_cost_limit = (avForm->vac_cost_limit >= 0) ? avForm->vac_cost_limit : ((autovacuum_vac_cost_limit >= 0) ? autovacuum_vac_cost_limit : VacuumCostLimit); vac_cost_delay = (avForm->vac_cost_delay >= 0) ? avForm->vac_cost_delay : ((autovacuum_vac_cost_delay >= 0) ? autovacuum_vac_cost_delay : VacuumCostDelay); } else { vac_scale_factor = autovacuum_vac_scale; vac_base_thresh = autovacuum_vac_thresh; anl_scale_factor = autovacuum_anl_scale; anl_base_thresh = autovacuum_anl_thresh; vac_cost_limit = (autovacuum_vac_cost_limit >= 0) ? autovacuum_vac_cost_limit : VacuumCostLimit; vac_cost_delay = (autovacuum_vac_cost_delay >= 0) ? autovacuum_vac_cost_delay : VacuumCostDelay; } vacthresh = (float4) vac_base_thresh + vac_scale_factor * reltuples; anlthresh = (float4) anl_base_thresh + anl_scale_factor * reltuples; /* * Note that we don't need to take special consideration for stat reset, * because if that happens, the last vacuum and analyze counts will be * reset too. */ elog(DEBUG3, "%s: vac: %.0f (threshold %.0f), anl: %.0f (threshold %.0f)", RelationGetRelationName(rel), vactuples, vacthresh, anltuples, anlthresh); /* Determine if this table needs vacuum or analyze. */ dovacuum = (vactuples > vacthresh); doanalyze = (anltuples > anlthresh); /* ANALYZE refuses to work with pg_statistics */ if (relid == StatisticRelationId) doanalyze = false; Assert(CurrentMemoryContext == AutovacMemCxt); if (classForm->relkind == RELKIND_RELATION) { if (dovacuum || doanalyze) elog(DEBUG2, "autovac: will%s%s %s", (dovacuum ? " VACUUM" : ""), (doanalyze ? " ANALYZE" : ""), RelationGetRelationName(rel)); /* * we must record tables that have a toast table, even if we currently * don't think they need vacuuming. */ if (dovacuum || doanalyze || OidIsValid(classForm->reltoastrelid)) { autovac_table *tab; tab = (autovac_table *) palloc(sizeof(autovac_table)); tab->relid = relid; tab->toastrelid = classForm->reltoastrelid; tab->dovacuum = dovacuum; tab->doanalyze = doanalyze; tab->vacuum_cost_limit = vac_cost_limit; tab->vacuum_cost_delay = vac_cost_delay; *vacuum_tables = lappend(*vacuum_tables, tab); } } else { Assert(classForm->relkind == RELKIND_TOASTVALUE); if (dovacuum) *toast_table_ids = lappend_oid(*toast_table_ids, relid); } RelationClose(rel);}/* * autovacuum_do_vac_analyze * Vacuum and/or analyze a list of tables; or all tables if relids = NIL */static voidautovacuum_do_vac_analyze(List *relids, bool dovacuum, bool doanalyze, bool freeze){ VacuumStmt *vacstmt; MemoryContext old_cxt; /* * The node must survive transaction boundaries, so make sure we create it * in a long-lived context */ old_cxt = MemoryContextSwitchTo(AutovacMemCxt); vacstmt = makeNode(VacuumStmt); /* * Point QueryContext to the autovac memory context to fake out the * PreventTransactionChain check inside vacuum(). Note that this is also * why we palloc vacstmt instead of just using a local variable. */ QueryContext = CurrentMemoryContext; /* Set up command parameters */ vacstmt->vacuum = dovacuum; vacstmt->full = false; vacstmt->analyze = doanalyze; vacstmt->freeze = freeze; vacstmt->verbose = false; vacstmt->relation = NULL; /* all tables, or not used if relids != NIL */ vacstmt->va_cols = NIL; /* Let pgstat know what we're doing */ autovac_report_activity(vacstmt, relids); vacuum(vacstmt, relids); pfree(vacstmt); MemoryContextSwitchTo(old_cxt);}/* * autovac_report_activity * Report to pgstat what autovacuum is doing * * We send a SQL string corresponding to what the user would see if the * equivalent command was to be issued manually. * * Note we assume that we are going to report the next command as soon as we're * done with the current one, and exiting right after the last one, so we don't * bother to report "<IDLE>" or some such. */#define MAX_AUTOVAC_ACTIV_LEN (NAMEDATALEN * 2 + 32)static voidautovac_report_activity(VacuumStmt *vacstmt, List *relids){ char activity[MAX_AUTOVAC_ACTIV_LEN]; /* * This case is not currently exercised by the autovac code. Fill it in * if needed. */ if (list_length(relids) > 1) elog(WARNING, "vacuuming >1 rel unsupported"); /* Report the command and possible options */ if (vacstmt->vacuum) snprintf(activity, MAX_AUTOVAC_ACTIV_LEN, "VACUUM%s%s%s", vacstmt->full ? " FULL" : "", vacstmt->freeze ? " FREEZE" : "", vacstmt->analyze ? " ANALYZE" : ""); else if (vacstmt->analyze) snprintf(activity, MAX_AUTOVAC_ACTIV_LEN, "ANALYZE"); /* Report the qualified name of the first relation, if any */ if (list_length(relids) > 0) { Oid relid = linitial_oid(relids); Relation rel; rel = RelationIdGetRelation(relid); if (rel == NULL) elog(WARNING, "cache lookup failed for relation %u", relid); else { char *nspname = get_namespace_name(RelationGetNamespace(rel)); int len = strlen(activity); snprintf(activity + len, MAX_AUTOVAC_ACTIV_LEN - len, " %s.%s", nspname, RelationGetRelationName(rel)); pfree(nspname); RelationClose(rel); } } pgstat_report_activity(activity);}/* * AutoVacuumingActive * Check GUC vars and report whether the autovacuum process should be * running. */boolAutoVacuumingActive(void){ if (!autovacuum_start_daemon || !pgstat_collect_startcollector || !pgstat_collect_tuplelevel) return false; return true;}/* * autovac_init * This is called at postmaster initialization. * * Annoy the user if he got it wrong. */voidautovac_init(void){ if (!autovacuum_start_daemon) return; if (!pgstat_collect_startcollector || !pgstat_collect_tuplelevel) { ereport(WARNING, (errmsg("autovacuum not started because of misconfiguration"), errhint("Enable options \"stats_start_collector\" and \"stats_row_level\"."))); /* * Set the GUC var so we don't fork autovacuum uselessly, and also to * help debugging. */ autovacuum_start_daemon = false; }}/* * IsAutoVacuumProcess * Return whether this process is an autovacuum process. */boolIsAutoVacuumProcess(void){ return am_autovacuum;}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -