openGauss vacuum analyze代码走读

vacuum\analyze 代码骨架

一些概念

vacuum、analyze在很多流程上是公用的,主要是analyze用了vacuum的很多流程,包括语法树主要结构体VacuumStmt,主要流程操作函数vacuum()等。

vacuum、analyze可以手动以SQL的形式触发,也可以通过autovacuum、autoanalyze进行自动触发。

触发流程入口

SQL语句触发主要流程

   语法分析中创建出结构体VacuumStmt。
   
   语义分析直接按照功能性语句挂到Query树中。
   
   功能性语句走ProcessUtility执行器,进入主函数:
   DoVacuumMppTable() {
   		解析SQL语句, 校验一些权限啥的,之后调了do_vacuum_mpp_table_other_node:
        		delta merge: begin_delta_merge()
       			verify: DoVerifyTableOtherNode
        
        		主要操作:vacuum()   
   }

autovacuum触发流程

AVCWorker线程执行,直接调用 vacuum() 函数,对某一张特定的表进行分析或整理。

可参考:openGauss autovacuum autoanalyze代码走读

主要操作函数 vacuum()

可以被语句调用,也会被avc调用。
入参: stmt, 表oid, 要不要搞toast,buffer使用策略

函数走读伪代码:

    一些安全性检查,上下文、计数统计等乱七八糟的初始化。
    
    获取要vacuum的目标关系的oid列表。如果给了则直接用,不然就是解析SQL,获取可能是一个表,也可能是一整个数据库的表等。
    relations = get_rel_oids(relid, vacstmt);  
    
    一些事务相关的操作
    
    try:
        置空vacuum_cxt,某个级别的vacuum信息,看上去和页面buffer命中率、速度控制等有关。
        
        遍历每个需要的关系。
        foreach (cur, relations) {
            设置一些状态、上下文变量等。
            
            如果指定了做vacuum则调用:vacuum_rel()
            如果指定了做analyze则调用:analyze_rel()
    
    catch:
        ....
        
    一些事务的处理
    如果vacuum了,并且不是avc的话,则vac_update_datfrozenxid更新datfrozenxid。(更新成pg_class的frozenxid最小值)。
        

关键函数: vacuum_rel(), 对一个关系做vacuum操作

VACOPT_NOWAIT: 取锁时不等待。只有avc才是true。

    一些事务的操作,一些检查等。
    
    如果不是vacuum full则设置一个全局flag,表示正在做lazy vacuum。
    X ProcArrayLock
    t_thrd.pgxact->vacuumFlags |= PROC_IN_VACUUM;
    D ProcArrayLock


    判断锁力度、分区的锁粒度。full需要加几级,lazy多少级。系统表又是多少级。
    if vacuumRelation(vacstmt->flags) && !(vacstmt->options & VACOPT_NOWAIT): 主动触发的vacuum relation
        判断一些安全性与权限:
            一些安全性的,vacuum系统表的权限校验等
            啥xc_maintenance_mode
            啥升级
        onerel = try_relation_open(relid, lmode); 按照算好的锁力度打开表
    elif vacuumPartition(vacstmt->flags) && !(vacstmt->options & VACOPT_NOWAIT):  主动触发vacuum 分区
        区分区分主表、分区、二级分区等,按照算好的力度打开表
        onepartrel  onepart     onerel  onesubpartrel等
    elif vacuumMainPartition(vacstmt->flags) && !(vacstmt->options & VACOPT_NOWAIT): 主动触发 vacuum 分区表
        onerel = try_relation_open(relid, lmode);  按照算好的力度打开表
    elif vacuumRelation(vacstmt->flags) && ConditionalLockRelationOid(relid, lmode): autovacuum时,仅尝试打开表
        已经在ConditionalLockRelationOid拿到锁了,再nolock随便打开一下。
    elif vacuumPartition(vacstmt->flags) && ConditionalLockRelationOid(relationid, lmodePartTable):  autovacuum时,仅尝试打开分区
        已经在ConditionalLockRelationOid拿到锁了,再nolock随便打开一下。
        还得处理一些分区的情况
    elif vacuumMainPartition(vacstmt->flags) && ConditionalLockRelationOid(relationid, lmodePartTable):   autovacuum时,仅尝试打开分区表
        已经在ConditionalLockRelationOid拿到锁了,再nolock随便打开一下。
        
    最后如果没有拿到或拿全锁,有些分区没拿到等。改打日志打日志,该放锁的放锁,
        退出。
    
    
    如果是列存表则打开cu、delta等
    又是一大坨校验,权限、安全、表类型、支不支持等,
    又是对啥ustore、分区、二级分区、物化视图、增量物化视图之类的,一大坨校验、加锁操作。
    
    
    switch:根据不同的表类型、不同的操作粒度,调用不同的接口来处理。
    - vacuum full ustore:  跳过
    - vacuum full 普通表:  cluster_rel()
    - vacuum full 分区:  vacuumFullPart()
    - vacuum full main分区表:GpiVacuumFullMainPartiton(), CBIVacuumFullMainPartiton()
    - vacuum ustore 分区表:UstoreVacuumMainPartitionGPIs()
    - vacuum 分区表: GPIVacuumMainPartition、CBIVacuumMainPartition
    - vacuum 其他(普通表、分区、普通ustore):  TableRelationVacuum
    
    提交事务等。
    
    如果有toast则触发一下toast的vacuum
    如果是物化视图的啥玩意则干点物化视图的啥玩意。
    
    释放一堆锁。
    

子函数 cluster_rel():对普通表做vacuum full

cluster是顺序聚簇操作,将元组按照某个索引的顺序聚簇,并重建整个表以及索引。
因此vacuum fullcluster的一个简化版,不需要排序,仅是重建。

重建原理过程比较简单。新建一个heap,然后遍历老heap,将元组堆积就好了。

但是需要注意,元组之间存在hot、ctid链,这些链不能破坏,因此在重建期间会有几个hash表,用于存放扫描到的映射关系,来建立这些元组间指针。

由于加的是八级锁,也不需要担心并发访问问题,先重建表,然后重建索引即可。


子函数 vacuumFullPart():对分区做vacuum full


子函数 GpiVacuumFullMainPartiton()、CBIVacuumFullMainPartiton:对分区表做vacuum full


子函数 UstoreVacuumMainPartitionGPIs():对Ustore分区表做vacuum


子函数 GPIVacuumMainPartition、CBIVacuumMainPartition():对分区表做vacuum


子函数 TableRelationVacuum()->lazy_vacuum_rel():大部份表的vacuum

适用范围:普通表、分区
一些概念:
lazy vacuum 会启动一个事物,但不会再任何页面上写自己的事务号。包括更新pg_class内相关数据的时候,都不会写自己的事务号,这可能与系统表的扫描snapshot相关。

    
    处理点二级分区的特殊情况。
    
    处理列存表:
        PASS
        结束
        
    
    处理下avc打日志的情况。
    
    vacuum_set_xid_limits(): 获取vacuum所需要的各种值:
        oldestxmin: 当前全局需要用到的最小的事务号。 这里需要注意,如果目标并是系统表,则这里使用catalogxmin,普通表则使用全局oldestxmin,这和逻辑复制有关,逻辑复制需要使用xlog当时的元数据,所以将系统表的底线xmin与普通表的分开,防止旧元数据被清理,也防止普通表长期得不到清理。
        freezelimit: 
        freezeTableLimit:
        
    获取relfrozenxid。在pg_class或pg_partition里获取
    判断是否需要全表扫描: relfrozenxid < freezeTableLimit  ?
    
    LVRelStats* vacrelstats;  一个看上去是用来记录vacuum进展状态以及一些计数的上下文。初始化。
    
    打开对应的索引。vac_open_part_indexes()、vac_open_indexes()
    
    
    【清理操作】
    lazy_scan_rel(){
        
        初始化一些东西。
            例如给每个索引都申请一些块内存结构,看上去是用来计数的。
            nblocks:获取文件一共有多少个block
            申请一段内存,用来在vacuum的时候用。内存大小主要是根据一个页面最多有多少个元组,来申请头部的一些容量。也和是否有索引有关,受maintenance_work_mem控制
            InitVacPrintStat(&printStats) 初始化计数结构。
            
        
        vacuum动作,主要有两遍。
        1、lazy_scan_heap()       一扫清理。执行heap page prune进行单页面内的清理。死元组位置列表、空闲空间等记录到vacrelstats。计算统计信息、标记页面头全可见等。清理索引。
        2、lazy_vacuum_heap()     二扫清理。因为在索引被清理之前,死元组是无法删除的,上一步已经清理索引了,所以需要二扫来清理死元组,且如果有记录死元组,则在上一步已经记录到vacrelstats了。
        一扫伪代码逻辑如下:
        lazy_scan_heap {
            又初始化了一些结构,像是计数的。
            
            根据vmap,找到第一个非全可见页面。next_not_all_visible_block。这里有一个逻辑,如果连续32个页面都全可见,那么除非入参指定了scan_all,否则会跳过这32个页面,不进行扫描。
            
            for 每个页面:
                判断检查是否满足32页面跳过的逻辑。是否进行跳过。
                
                异步IO预读?enable_adio_function
                
                ???有点没看懂的  close to overrunning the available space
                
                pin住当前页的vmap
                
                buf = ReadBufferExtended()加载页面。
                需要加X锁独占页面,尝试加X锁失败则尝试加S锁判断一些freeze的情况,如果不要紧的话,就跳过这个页面。
                
                获取页面
                
                if 这是个新页面或空页面,就稍微规整一下页面头,置置脏,记录一下fsm或vmap等,然后下一个。
                
                页面剪枝清理。heap page prune只会去删除页面元组的data部分,清理hot链,不会删除指针部分。
                heap_page_prune() { 
                    创建一个PruneState,记录剪枝状态。
                    
                    for 每个元组:
                        已经被前边链式访问时清理过,或者死元组,或者是个unused指针,则跳过。
                        记录需要修建的链,需要修剪的元组等,到PruneState
                    
                    根据PruneState进行修剪。 (根据元组页面结构,显然这里是要先遍历记录,然后统一修剪的,应该无法做到边遍历边修剪)
                    
                    维护pageheader的一些flag,如pagefull之类的。
                    
                    返回删除了的元组数。
                }
                    
                for 每个元组:  (重新遍历页面,搞一些计数之类的东西。判断allvisible之类的。)
                    看看是不是重定向了或已经知道的死元组,做一些啥处理。
                    检测vacuum mvcc,根据元组状态,做一些啥东西。例如更新元组状态、记录计数、等。
                    
                根据刚才遍历统计记录的结果,按需执行freeze、invalid、等,做一些操作:
                    log_heap_freeze: 冻结页面元组
                    log_heap_invalid: 给页面置成invalid?
                    lazy_vacuum_page:如果没有索引的话,直接清理死元组。
                
                如果页面全可见的话,更新vm
                
                更新fsm
            
            
            vac_estimate_reltuples:根据之前的扫描统计清理结果,重新估算reltuples
            
            foreach indedx do lazy_vacuum_index():如果有死元组,则清理每个索引
                调用ambulkdelete的函数,btree是 btbulkdelete
                调用btvacuumscan,清理。
                并更新和返回
            
            foreach index do lazy_cleanup_index():
                调用amvacuumcleanup函数,btree是 btvacuumcleanup
                看上去如果btbulkdelete做了,这步就只做一个fsm的更新,不然还会btvacuumscan一遍。
                
            
            打印点日志,更新一下printStats
            
            返回indstats,索引数量
        }
        
        二扫伪代码逻辑如下:
        lazy_vacuum_heap(){
            在上一步的lazy_scan_heap中,已经给hot链、索引清理完了,现在二刷,就可以清理那些没有指针指到的死元组了。
            但是头部指针仍然不能动,因为如果头也清理了,必然影响其他的头的顺序,所以只会给那个指针的flag制成unusable。后续可以直接复用。
            
            还需要记录fsm
        }
        
        
        整理FSM
        FreeSpaceMapVacuum
        
        将索引计数加到vacuum计数上。
        
    }
    
    看看能不能截断最后面的空block(存在日志复制的时候不会,可能redo到时候会有问题,不懂)
    
    visibilitymap_count()更新vmap
    
    更新数据特征统计信息:pg_class、pg_partition内的relfrozenxid、行数、全可见页面数量等等。
    
    关闭对应的索引
    
    pstat上报vacuum计数统计信息
    
    avc Log_autovacuum_min_duration打印日志
        
        

关键函数 analyze_rel():对一个关系做analyze操作

analyze相对于vacuum来说比较简单了就。

相关数据结构
struct VacAttrStats:统计采样分析算法的计算上下文。用在对某一列进行统计算法计算的时候,存储这列的数据类型、各种统计信息计算算法的中间变量数据、结果等。

主要代码

    Relation onerel = analyze_get_relation(relid, vacstmt);  打开需要analyze的表
    
    一堆校验
    
    【开始的analyze】
    analyze_rel_internal() :
        又是一堆校验,啥系统表、临时表、pg_statistic啥玩意的。
        
        如果是外表,则调用fdw相关hook,然后基本就结束了
        
        relpages:获取表的页面数量。
        
        do_analyze_rel():
            啥玩意检查和日志
            
           任务进度上下文,记录analyze任务的进度、状态
            caller_context = do_analyze_preprocess()
			
			算法变量上下文。为需要analyze的每一列创建算法变量上下文,获取这些列上是不是有索引等信息
            VacAttrStats** vacattrstats = get_vacattrstats_by_vacstmt(onerel, vacstmt, $attr_cnd, &nindexes, &indexdata, &hasindex, inh, &Irel):

			targrows:  采样需要的行数。与表、索引都有关。
			
			rows = get_total_rows<false>(...)  采样。
					acquire_sample_rows:  采样,使用BlockSampler模块进行采样,可见性判断用HeapTupleStatisfiesVacuum(Oldestxmin)。
										  因此可以认为是按页面进行采样。
	        
	        bool ret = do_analyze_samplerows():  对采样行进行统计。
 			
			更新pg_class,汇报pgstat
			
			如果不是vacuum analyze语句触发的话,需要清理索引?
			
			关闭索引
			autoanalyze的的话打印相关日志。
			do_analyze_finalize():关闭事务,清理内存等。

其他函数 vacuum_delay_point():控制vacuum速度

控制vacuum速度的东西,还和几个vacuum cost、delay等参数有关。
函数存在于vacuum流程中的各个点。

  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
手动vacuumanalyze是用来优化数据库性能的操作。手动vacuum用于回收已删除行占用的空间,以减少数据库文件的大小。而手动analyze则用于更新数据库的统计信息,以便查询优化器能够更好地选择执行计划。 在引用\[1\]中提到了一些与手动vacuumanalyze相关的信息,比如上次手动vacuumanalyze的时间,以及它们的总计次数。这些信息可以帮助我们了解数据库的维护情况。 另外,在引用\[2\]中提到了一些与自动vacuum和自动analyze相关的参数。autovacuum_analyze_threshold参数用于设置自动分析的阈值,当表中的行数超过该阈值时,自动分析将被触发。而autovacuum_analyze_scale_factor参数用于设置自动分析的比例因子,它与表的大小成比例,用于确定自动分析的触发时机。 总的来说,手动vacuumanalyze是用来优化数据库性能的重要操作。通过合理地设置自动vacuum和自动analyze的参数,以及根据需要进行手动vacuumanalyze,可以保持数据库的健康状态,并提高查询性能。 #### 引用[.reference_title] - *1* [PostgreSQL10基础(6)AnalyzeVacuum](https://blog.csdn.net/supermancoke/article/details/104612822)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [PostgreSQL VACUUMANALYZE 最佳实践技巧](https://blog.csdn.net/u012551524/article/details/120548763)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值