您好,登錄后才能下訂單哦!
本節簡單介紹了PostgreSQL手工執行vacuum的主處理流程,主要分析了ExecVacuum->vacuum->vacuum_rel函數的實現邏輯。
宏定義
Vacuum和Analyze命令選項
/* ----------------------
* Vacuum and Analyze Statements
* Vacuum和Analyze命令選項
*
* Even though these are nominally two statements, it's convenient to use
* just one node type for both. Note that at least one of VACOPT_VACUUM
* and VACOPT_ANALYZE must be set in options.
* 雖然在這里有兩種不同的語句,但只需要使用統一的Node類型即可.
* 注意至少VACOPT_VACUUM/VACOPT_ANALYZE在選項中設置.
* ----------------------
*/
typedef enum VacuumOption
{
VACOPT_VACUUM = 1 << 0, /* do VACUUM */
VACOPT_ANALYZE = 1 << 1, /* do ANALYZE */
VACOPT_VERBOSE = 1 << 2, /* print progress info */
VACOPT_FREEZE = 1 << 3, /* FREEZE option */
VACOPT_FULL = 1 << 4, /* FULL (non-concurrent) vacuum */
VACOPT_SKIP_LOCKED = 1 << 5, /* skip if cannot get lock */
VACOPT_SKIPTOAST = 1 << 6, /* don't process the TOAST table, if any */
VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7 /* don't skip any pages */
} VacuumOption;
VacuumStmt
存儲vacuum命令的option&Relation鏈表
typedef struct VacuumStmt
{
NodeTag type;//Tag
//VacuumOption位標記
int options; /* OR of VacuumOption flags */
//VacuumRelation鏈表,如為NIL-->所有Relation.
List *rels; /* list of VacuumRelation, or NIL for all */
} VacuumStmt;
VacuumParams
vacuum命令參數
/*
* Parameters customizing behavior of VACUUM and ANALYZE.
* 客戶端調用VACUUM/ANALYZE時的定制化參數
*/
typedef struct VacuumParams
{
//最小freeze age,-1表示使用默認
int freeze_min_age; /* min freeze age, -1 to use default */
//掃描整個table的freeze age
int freeze_table_age; /* age at which to scan whole table */
//最小的multixact freeze age,-1表示默認
int multixact_freeze_min_age; /* min multixact freeze age, -1 to
* use default */
//掃描全表的freeze age,-1表示默認
int multixact_freeze_table_age; /* multixact age at which to scan
* whole table */
//是否強制wraparound?
bool is_wraparound; /* force a for-wraparound vacuum */
//以毫秒為單位的最小執行閾值
int log_min_duration; /* minimum execution threshold in ms at
* which verbose logs are activated, -1
* to use default */
} VacuumParams;
VacuumRelation
VACUUM/ANALYZE命令的目標表信息
/*
* Info about a single target table of VACUUM/ANALYZE.
* VACUUM/ANALYZE命令的目標表信息.
*
* If the OID field is set, it always identifies the table to process.
* Then the relation field can be NULL; if it isn't, it's used only to report
* failure to open/lock the relation.
* 如設置了OID字段,該值通常是將要處理的數據表.
* 那么關系字段可以為空;如果不是,則僅用于報告未能打開/鎖定關系。
*/
typedef struct VacuumRelation
{
NodeTag type;
RangeVar *relation; /* table name to process, or NULL */
Oid oid; /* table's OID; InvalidOid if not looked up */
List *va_cols; /* list of column names, or NIL for all */
} VacuumRelation;
vacuum_rel():vacuum one heap relation
大體邏輯如下:
1.啟動事務,快照入棧,設置事務狀態為PROC_IN_VACUUM
2.打開relation,請求合適的鎖(FULL->AccessExclusiveLock,Concurrent->ShareUpdateExclusiveLock)
3.執行相應的檢查(owner/relkind/臨時表/分區表…)
4.執行TOAST相關處理
5.執行前期準備工作(切換user等)
6.執行實際的工作
6.1.FULL->cluster_rel
6.2.Concurrent->heap_vacuum_rel
7.執行收尾工作
8.如存在TOAST,在執行TOAST表的vacuum
/*
* vacuum_rel() -- vacuum one heap relation
* vacuum_rel() -- vacuum一個heap relation
*
* relid identifies the relation to vacuum. If relation is supplied,
* use the name therein for reporting any failure to open/lock the rel;
* do not use it once we've successfully opened the rel, since it might
* be stale.
* relid是需要vacuum的relation.
* 如提供了relation,使用其中的名稱報告任何未能打開/鎖定rel的情況.
* 只要我們成功打開了relation,那么就不需要再使用relation了,因為該變量可能已失效.
*
* Returns true if it's okay to proceed with a requested ANALYZE
* operation on this table.
* 如執行在請求的數據表上執行ANALYZE操作是OK的,返回T.
*
* 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.
* 一次只做一個堆會帶來額外的開銷,因為我們需要在vacuum之前檢查它是否再次存在。
* 我們這樣做的原因是,vacuuming可以分散到許多小事務中。
* 否則,兩階段鎖定將要求我們在一次vacuum時鎖定整個數據庫。
*
* At entry and exit, we are not inside a transaction.
* 在入口和退出處,不在事務中.
*/
static bool
vacuum_rel(Oid relid, RangeVar *relation, int options, VacuumParams *params)
{
LOCKMODE lmode;
Relation onerel;
LockRelId onerelid;
Oid toast_relid;
Oid save_userid;
int save_sec_context;
int save_nestlevel;
Assert(params != NULL);
/* Begin a transaction for vacuuming this relation */
//vacuuming relation時開啟一個事務
StartTransactionCommand();
/*
* Functions in indexes may want a snapshot set. Also, setting a snapshot
* ensures that RecentGlobalXmin is kept truly recent.
* 處理索引上的函數可能需要快照集.
* 同樣的設置一個快照可以確保RecentGlobalXmin保持為最近的.
*/
PushActiveSnapshot(GetTransactionSnapshot());
if (!(options & VACOPT_FULL))
{
/*
* In lazy vacuum, we can set the PROC_IN_VACUUM flag, which lets
* other concurrent VACUUMs know that they can ignore this one while
* determining their OldestXmin. (The reason we don't set it during a
* full VACUUM is exactly that we may have to run user-defined
* functions for functional indexes, and we want to make sure that if
* they use the snapshot set above, any tuples it requires can't get
* removed from other tables. An index function that depends on the
* contents of other tables is arguably broken, but we won't break it
* here by violating transaction semantics.)
* 在lazy vacuum,我們可以設置PROC_IN_VACUUM標記,這樣可以讓其他并發的VACUUMs
* 知道他們在確定OldestXmin時可以忽略這個.
* (不在full VACUUM中設置的原因正好是我們可能不得不為函數索引執行用戶自定義函數,
* 我們希望確保如果使用上面的快照集,快照需要的所有元組不能在其他表中清除).
* 依賴于其他表的內容的索引函數可能會被破壞,但是我們不會違反事務語義來破壞它。
*
* We also set the VACUUM_FOR_WRAPAROUND flag, which is passed down by
* autovacuum; it's used to avoid canceling a vacuum that was invoked
* in an emergency.
* 我們還設置了VACUUM_FOR_WRAPAROUND標記,通過autovacuum傳遞下去,
* 這可以用于避免取消在緊急情況下啟動的vacuum進程.
*
* Note: these flags remain set until CommitTransaction or
* AbortTransaction. We don't want to clear them until we reset
* MyPgXact->xid/xmin, else OldestXmin might appear to go backwards,
* which is probably Not Good.
* 注意:這些標記一直保留到CommitTransaction或AbortTransaction.
* 我們不希望清除這些標記,直至重置MyPgXact->xid/xmin,否則舊的xmin可能會出現倒退,這可能不好.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyPgXact->vacuumFlags |= PROC_IN_VACUUM;
if (params->is_wraparound)
MyPgXact->vacuumFlags |= PROC_VACUUM_FOR_WRAPAROUND;
LWLockRelease(ProcArrayLock);
}
/*
* Check for user-requested abort. Note we want this to be inside a
* transaction, so xact.c doesn't issue useless WARNING.
* 檢查用戶請求的abort.
* 注意我們希望這個操作在事務中,因此xact.c不需要發起不必要的WARNING.
*/
CHECK_FOR_INTERRUPTS();
/*
* 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.
* 確定需要的鎖類型.
* 唯一鎖 for FULL vacuum.
* ShareUpdateExclusiveLock for 普通的并發vacuum.
* 不管哪種途徑,可以確保的是沒有那個后臺進程同時在vacuum同一張表.
*/
lmode = (options & VACOPT_FULL) ? AccessExclusiveLock : ShareUpdateExclusiveLock;
/* open the relation and get the appropriate lock on it */
//打開relation,獲取合適的鎖.
onerel = vacuum_open_relation(relid, relation, params, options, lmode);
/* leave if relation could not be opened or locked */
//如relation不能被打開/鎖定,則退出
if (!onerel)
{
PopActiveSnapshot();
CommitTransactionCommand();
return false;
}
/*
* Check if relation needs to be skipped based on ownership. This check
* happens also when building the relation list to vacuum for a manual
* operation, and needs to be done additionally here as VACUUM could
* happen across multiple transactions where relation ownership could have
* changed in-between. Make sure to only generate logs for VACUUM in this
* case.
* 檢查relation是否可以跳過.
* 在構建relation鏈表準備vacuum手工操作也需要檢查,
* 并且需要在這里進行額外的操作,因為VACUUM可能發生在多個事務之間,
* 而這些事務之間的關系所有權可能已經發生了變化.
* 在這種情況下,確保只為VACUUM生成日志。
*/
if (!vacuum_is_relation_owner(RelationGetRelid(onerel),
onerel->rd_rel,
options & VACOPT_VACUUM))
{
relation_close(onerel, lmode);
PopActiveSnapshot();
CommitTransactionCommand();
return false;
}
/*
* Check that it's of a vacuumable relkind.
* 檢查relation是vacuumable relkind
*/
if (onerel->rd_rel->relkind != RELKIND_RELATION &&
onerel->rd_rel->relkind != RELKIND_MATVIEW &&
onerel->rd_rel->relkind != RELKIND_TOASTVALUE &&
onerel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
{
ereport(WARNING,
(errmsg("skipping \"%s\" --- cannot vacuum non-tables or special system tables",
RelationGetRelationName(onerel))));
relation_close(onerel, lmode);
PopActiveSnapshot();
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.)
* 忽略其他進程產生的臨時表.
* 如果嘗試vacuum這些表會導致相當"不愉快"的事情發生,因為這些表的內容可能在磁盤上不是最新的.
* (在這里不會產生警告,它只會在數據庫范圍內的VACUUM引起抖動。)
*/
if (RELATION_IS_OTHER_TEMP(onerel))
{
relation_close(onerel, lmode);
PopActiveSnapshot();
CommitTransactionCommand();
return false;
}
/*
* Silently ignore partitioned tables as there is no work to be done. The
* useful work is on their child partitions, which have been queued up for
* us separately.
* 忽略分區表.
* 有用的工作只是在他們的子分區上,這些表已經加入處理隊列中.
*/
if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{
relation_close(onerel, lmode);
PopActiveSnapshot();
CommitTransactionCommand();
/* It's OK to proceed with ANALYZE on this table */
return true;
}
/*
* 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.
* 獲取session-level鎖.
* 這可以保護在跨多事務時對relation的訪問,
* 因此我們可以安全的vacuum關系的TOAST表,在沒有進程刪除父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;
LockRelationIdForSession(&onerelid, lmode);
/*
* Remember the relation's TOAST relation for later, if the caller asked
* us to process it. In VACUUM FULL, though, the toast table is
* automatically rebuilt by cluster_rel so we shouldn't recurse to it.
* 如調用者要求我們處理TOAST,則為后續的處理標記relation's TOAST relation
* 但在VACUUM FULL,toast table會通過cluster_rel自動重建,因此無需處理.
*/
if (!(options & VACOPT_SKIPTOAST) && !(options & VACOPT_FULL))
toast_relid = onerel->rd_rel->reltoastrelid;
else
toast_relid = InvalidOid;
/*
* Switch to the table owner's userid, so that any index functions are run
* as that user. Also lock down security-restricted operations and
* arrange to make GUC variable changes local to this command. (This is
* unnecessary, but harmless, for lazy VACUUM.)
* 切換至owner的userid,以便所有的索引函數可以作為此用戶運行.
* 同時,鎖定security-restricted操作并重新組織,為該命令修改本地GUC變量.
* (這其實是不需要的,但悲催的是,需要為lazy VACUUM設置)
*/
GetUserIdAndSecContext(&save_userid, &save_sec_context);
SetUserIdAndSecContext(onerel->rd_rel->relowner,
save_sec_context | SECURITY_RESTRICTED_OPERATION);
save_nestlevel = NewGUCNestLevel();
/*
* Do the actual work --- either FULL or "lazy" vacuum
* 執行實際的工作.
* 不管是FULL 或"lazy" vacuum
*/
if (options & VACOPT_FULL)
{
int cluster_options = 0;
/* close relation before vacuuming, but hold lock until commit */
//在vacuuming前關閉relation,持有鎖直至commit
relation_close(onerel, NoLock);
onerel = NULL;
if ((options & VACOPT_VERBOSE) != 0)
cluster_options |= CLUOPT_VERBOSE;
/* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
//VACUUM FULL現在是CLUSTER的一個變體,詳細請查看cluster.c
cluster_rel(relid, InvalidOid, cluster_options);
}
else
heap_vacuum_rel(onerel, options, params, vac_strategy);
/* Roll back any GUC changes executed by index functions */
//回滾索引函數修改的GUC參數
AtEOXact_GUC(false, save_nestlevel);
/* Restore userid and security context */
//恢復userid和安全上下文
SetUserIdAndSecContext(save_userid, save_sec_context);
/* all done with this class, but hold lock until commit */
//所有的工作已完成,但持有鎖,直到提交
if (onerel)
relation_close(onerel, NoLock);
/*
* Complete the transaction and free all temporary memory used.
* 完成事務,并釋放索引臨時內存.
*/
PopActiveSnapshot();
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.
* 如果relation有第二個toast rel,vacuum仍然會在主表上持有session lock.
* 注意analyze不會在toast table上完成.這樣操作沒有問題,
* 因為toaster總是使用硬編碼的索引訪問,而統計數據對于toast relations來說不重要.
*/
if (toast_relid != InvalidOid)
vacuum_rel(toast_relid, NULL, options, params);
/*
* Now release the session-level lock on the master table.
* 現在可以釋放session-level鎖了.
*/
UnlockRelationIdForSession(&onerelid, lmode);
/* Report that we really did it. */
//DONE!
return true;
}
測試腳本
17:19:28 (xdb@[local]:5432)testdb=# vacuum t1;
啟動gdb,設置斷點
(gdb) b vacuum_rel
Breakpoint 2 at 0x6bb319: file vacuum.c, line 1310.
(gdb) c
Continuing.
Breakpoint 2, vacuum_rel (relid=42634, relation=0x22948d0, options=1, params=0x7fff403d8880) at vacuum.c:1310
1310 bool rel_lock = true;
(gdb)
輸入參數
relid=42634 —> t1
relation=0x22948d0 —> t1
使用默認的vacuum參數
###
16:20:00 (xdb@[local]:5432)testdb=# select oid,relname,reltype from pg_class where oid=42634;
oid | relname | reltype
-------+---------+---------
42634 | t1 | 42636
(1 row)
###
(gdb) p *relation
$5 = {type = T_RangeVar, catalogname = 0x0, schemaname = 0x0, relname = 0x22948b0 "t1", inh = true,
relpersistence = 112 'p', alias = 0x0, location = 7}
(gdb)
(gdb) p *params
$6 = {freeze_min_age = -1, freeze_table_age = -1, multixact_freeze_min_age = -1, multixact_freeze_table_age = -1,
is_wraparound = false, log_min_duration = -1}
(gdb)
啟動事務,快照入棧,設置事務狀態為PROC_IN_VACUUM
(gdb) n
1312 Assert(params != NULL);
(gdb)
1315 StartTransactionCommand();
(gdb)
1321 PushActiveSnapshot(GetTransactionSnapshot());
(gdb)
1323 if (!(options & VACOPT_FULL))
(gdb)
1345 LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
(gdb)
1346 MyPgXact->vacuumFlags |= PROC_IN_VACUUM;
(gdb)
1347 if (params->is_wraparound)
(gdb)
1349 LWLockRelease(ProcArrayLock);
(gdb)
1356 CHECK_FOR_INTERRUPTS();
(gdb)
打開relation,請求合適的鎖(FULL->AccessExclusiveLock,Concurrent->ShareUpdateExclusiveLock)
(gdb)
1363 lmode = (options & VACOPT_FULL) ? AccessExclusiveLock : ShareUpdateExclusiveLock;
(gdb)
1374 if (!(options & VACOPT_NOWAIT))
(gdb)
1375 onerel = try_relation_open(relid, lmode);
(gdb)
1388 if (!onerel)
(gdb) p *onerel
$7 = {rd_node = {spcNode = 1663, dbNode = 16402, relNode = 42634}, rd_smgr = 0x0, rd_refcnt = 1, rd_backend = -1,
rd_islocaltemp = false, rd_isnailed = false, rd_isvalid = true, rd_indexvalid = 0 '\000', rd_statvalid = false,
rd_createSubid = 0, rd_newRelfilenodeSubid = 0, rd_rel = 0x7f2d2571bbb8, rd_att = 0x7f2d25637268, rd_id = 42634,
rd_lockInfo = {lockRelId = {relId = 42634, dbId = 16402}}, rd_rules = 0x0, rd_rulescxt = 0x0, trigdesc = 0x0,
rd_rsdesc = 0x0, rd_fkeylist = 0x0, rd_fkeyvalid = false, rd_partkeycxt = 0x0, rd_partkey = 0x0, rd_pdcxt = 0x0,
rd_partdesc = 0x0, rd_partcheck = 0x0, rd_indexlist = 0x0, rd_oidindex = 0, rd_pkindex = 0, rd_replidindex = 0,
rd_statlist = 0x0, rd_indexattr = 0x0, rd_projindexattr = 0x0, rd_keyattr = 0x0, rd_pkattr = 0x0, rd_idattr = 0x0,
rd_projidx = 0x0, rd_pubactions = 0x0, rd_options = 0x0, rd_index = 0x0, rd_indextuple = 0x0, rd_amhandler = 0,
rd_indexcxt = 0x0, rd_amroutine = 0x0, rd_opfamily = 0x0, rd_opcintype = 0x0, rd_support = 0x0, rd_supportinfo = 0x0,
rd_indoption = 0x0, rd_indexprs = 0x0, rd_indpred = 0x0, rd_exclops = 0x0, rd_exclprocs = 0x0, rd_exclstrats = 0x0,
rd_amcache = 0x0, rd_indcollation = 0x0, rd_fdwroutine = 0x0, rd_toastoid = 0, pgstat_info = 0x2313030}
(gdb)
執行相應的檢查(owner/relkind/臨時表/分區表…)
(gdb) n
1442 if (!(pg_class_ownercheck(RelationGetRelid(onerel), GetUserId()) ||
(gdb) p GetUserId()
$8 = 10
(gdb) p RelationGetRelid(onerel)
$9 = 42634
(gdb) n
1466 if (onerel->rd_rel->relkind != RELKIND_RELATION &&
(gdb)
1487 if (RELATION_IS_OTHER_TEMP(onerel))
(gdb)
1500 if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
(gdb)
1519 onerelid = onerel->rd_lockInfo.lockRelId;
(gdb)
1520 LockRelationIdForSession(&onerelid, lmode);
(gdb) p onerelid
$10 = {relId = 42634, dbId = 16402}
(gdb)
執行TOAST相關處理
(gdb) n
1527 if (!(options & VACOPT_SKIPTOAST) && !(options & VACOPT_FULL))
(gdb)
1528 toast_relid = onerel->rd_rel->reltoastrelid;
(gdb)
1538 GetUserIdAndSecContext(&save_userid, &save_sec_context);
(gdb) p toast_relid
$11 = 0
(gdb)
執行前期準備工作(切換user等)
(gdb) n
1539 SetUserIdAndSecContext(onerel->rd_rel->relowner,
(gdb)
1541 save_nestlevel = NewGUCNestLevel();
(gdb)
1546 if (options & VACOPT_FULL)
(gdb) p save_nestlevel
$12 = 2
(gdb) n
執行實際的工作
Concurrent->heap_vacuum_rel(11.1版本為lazy_vacuum_rel函數)
(gdb) n
1557 lazy_vacuum_rel(onerel, options, params, vac_strategy);
(gdb)
執行收尾工作
(gdb)
1560 AtEOXact_GUC(false, save_nestlevel);
(gdb)
1563 SetUserIdAndSecContext(save_userid, save_sec_context);
(gdb)
1566 if (onerel)
(gdb)
1567 relation_close(onerel, NoLock);
(gdb)
1572 PopActiveSnapshot();
(gdb)
1573 CommitTransactionCommand();
(gdb)
1582 if (toast_relid != InvalidOid)
(gdb)
如存在TOAST,在執行TOAST表的vacuum
(gdb)
1582 if (toast_relid != InvalidOid)
(gdb)
1588 UnlockRelationIdForSession(&onerelid, lmode);
(gdb)
1591 return true;
(gdb)
執行完成
1592 }
(gdb)
vacuum (options=1, relations=0x23525f0, params=0x7fff403d8880, bstrategy=0x2352478, isTopLevel=true) at vacuum.c:344
344 if (options & VACOPT_ANALYZE)
(gdb)
DONE!
PG Source Code
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。