vaccum 函数
exec_simple_query->PortalRun->PortalRunMulti->PortalRunUtility->ProcessUtility->standard_ProcessUtility->vacuum 逻辑。
- 检验这个 vacuum 命令不是在事务块中执行的
- 统计 pg_database 中所有 dead 的 database 通知 collector 去 drop 这些 database,统计自己用户的 database 中 pg_class 的所有表和 pg_proc 的所有 functions,报告给 collector。
- 创建 vacuum 用的上下文
- 对dispatcher,为了快速释放锁,需要提交当前事务(不在事务中执行vacuum),但executor不提交事务。
- 对每个表执行 vacuumStatement_Relation
- 重新开启事务去对应postgresMain中的结束事务。
- 更新 frozen xid 并 truncate pg_clog
vacuumStatement_Relation
- 获取当前的快照(PushActiveSnapshot(GetTransactionSnapshot()))
- 打开表,检查有没有权限执行 vacuum,及是否是可清理的表(外表,特殊系统表,非表不可以清理)
- 不能清理其它namespace的临时表(其它backend)
- 获取session级别的锁(以此保证清理toast表时,其主表不会更改)
- 将 vaccum parenttable 或 vaccum database 相关的具体表展开(vacuumStatement_AssignRelation)
- 执行清理 vacuum_rel
- 对于 append only 表使用复制的方式清理旧元组
- 释放 session 锁
vacuum_rel
- 记录下这个表的 toast 和 append only 的 segment。
- 切换至表的创建用户(用于执行表的索引函数)
- 保存现有的全局变量 NewGUCNestLevel()
- 对于 dispatcher,打开所有可以vaccum的索引,要加共享锁,并分发出去(dispatchVacuum)并更新统计(vac_update_relstats_from_list)
- 对于executor,执行 lazy vacuum(lazy_vacuum_rel)
- 还原全局变量 (AtEOXact_GUC)
- 切换回原用户(SetUserIdAndSecContext)
- 在dispatcher,对 append only 表重新统计压缩或 drop 后的元组数 (UpdateMasterAosegTotalsFromSegments)
- 关闭表
- dispatcher 提交这一次vacuum事务
- dispatcher 继续清理相关 toast 表(还持有着session锁,因此可以做这一步)
- dispatcher 继续清理append only 表的 segment 表,block directory 表还有 visimap 表。
lazy_vacuum_rel
- 更新 oldest-Xmin,计算新的可以 freeze 的 xid 位置
- 对 append only 表可优化执行 (因为不存在更新链,所以可以直接标记live或dead,lazy_vacuum_aorel)
- 打开表的所有索引
- 执行 vaccum (lazy_scan_heap)
- 跳过元组全可见的页(没有修改过)
- 检查是否有中断和是否到了该休息一下的时候
- pin 页可见性统计页
- 读入一页
- 获取这页的锁
- 如果获取不成功,但是没有到为防止事务号回卷而必须值全表清理的程度,就可以先跳过这一页
- 如果读入的是新页,可能这一页还没被读入这页的进程初始化,我们放弃一次锁再重新获取,如果还是没初始化,那这个进程可能死掉了,我们可以自己把这页初始化
- 收集和清理过老的元组,统计页中被清理的元组数(heap_page_prune)
- 读每个itemid并找到相关的更新链条,执行 heap_prune_chain
- 跳过itemid上标记 unused,redirected,dead的元组
- 判断这一个元组对所有事务可见 HeapTupleSatisfiesVacuum
- 更新本页最小事务号
- 更新一条链的起始位置到pstate的redirect数组上,如果一条链全删除了,就更新到pstate的dead数组上
- heap_page_prune_execute
- 将 redirect 的元组位置更新到对应的根 itemid 上
- 将 dead 的元组位置更新到对应的 itemid 上
- 将所有未使用的元组位置更新到对应的 itemid 上
- 将元组数据压缩到一起(清除中间的碎片PageRepairFragmentation)
- 写 XLOG_HEAP2_CLEAN 的事务日志
- 更新页的 lsn
- 更新表中元组的统计信息 (pgstat_update_heap_dead_tuples)
- 读每个itemid并找到相关的更新链条,执行 heap_prune_chain
- 再次扫描标记frozen元组,同时清理新dead的元组
- 对死亡的itemid做标记
- 对未死亡元组判断是否需要freeze(heap_execute_freeze_tuple),写 XLOG_HEAP2_FREEZE_PAGE 日志
- 标记 all_visible,如果此页全可见了
- 如果发现了 dead 元组,就清除 all_visible 标记
- 累加非空页的页数
- 记录空闲空间
- 先移除索引(lazy_vacuum_index),再清理页(lazy_vacuum_heap)
- 更新索引上的统计(lazy_cleanup_index)