文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

PostgreSQL 源码解读(127)- MVCC#11(vacuum过程-vacuum_rel函数)

2024-04-02 19:55

关注

本节简单介绍了PostgreSQL手工执行vacuum的主处理流程,主要分析了ExecVacuum->vacuum->vacuum_rel函数的实现逻辑。

一、数据结构

宏定义
Vacuum和Analyze命令选项




typedef enum VacuumOption
{
    VACOPT_VACUUM = 1 << 0,     
    VACOPT_ANALYZE = 1 << 1,    
    VACOPT_VERBOSE = 1 << 2,    
    VACOPT_FREEZE = 1 << 3,     
    VACOPT_FULL = 1 << 4,       
    VACOPT_SKIP_LOCKED = 1 << 5,    
    VACOPT_SKIPTOAST = 1 << 6,  
    VACOPT_DISABLE_PAGE_SKIPPING = 1 << 7   
} VacuumOption;

VacuumStmt
存储vacuum命令的option&Relation链表



typedef struct VacuumStmt
{
    NodeTag     type;//Tag
    //VacuumOption位标记
    int         options;        
    //VacuumRelation链表,如为NIL-->所有Relation.
    List       *rels;           
} VacuumStmt;

VacuumParams
vacuum命令参数




typedef struct VacuumParams
{
    //最小freeze age,-1表示使用默认
    int         freeze_min_age; 
    //扫描整个table的freeze age
    int         freeze_table_age;   
    //最小的multixact freeze age,-1表示默认
    int         multixact_freeze_min_age;   
    //扫描全表的freeze age,-1表示默认
    int         multixact_freeze_table_age; 
    //是否强制wraparound?
    bool        is_wraparound;  
    //以毫秒为单位的最小执行阈值
    int         log_min_duration;   
} VacuumParams;

VacuumRelation
VACUUM/ANALYZE命令的目标表信息




typedef struct VacuumRelation
{
    NodeTag     type;
    RangeVar   *relation;       
    Oid         oid;            
    List       *va_cols;        
} 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




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);
    
    //vacuuming relation时开启一个事务
    StartTransactionCommand();
    
    PushActiveSnapshot(GetTransactionSnapshot());
    if (!(options & VACOPT_FULL))
    {
        
        LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
        MyPgXact->vacuumFlags |= PROC_IN_VACUUM;
        if (params->is_wraparound)
            MyPgXact->vacuumFlags |= PROC_VACUUM_FOR_WRAPAROUND;
        LWLockRelease(ProcArrayLock);
    }
    
    CHECK_FOR_INTERRUPTS();
    
    lmode = (options & VACOPT_FULL) ? AccessExclusiveLock : ShareUpdateExclusiveLock;
    
    //打开relation,获取合适的锁.
    onerel = vacuum_open_relation(relid, relation, params, options, lmode);
    
    //如relation不能被打开/锁定,则退出
    if (!onerel)
    {
        PopActiveSnapshot();
        CommitTransactionCommand();
        return false;
    }
    
    if (!vacuum_is_relation_owner(RelationGetRelid(onerel),
                                  onerel->rd_rel,
                                  options & VACOPT_VACUUM))
    {
        relation_close(onerel, lmode);
        PopActiveSnapshot();
        CommitTransactionCommand();
        return false;
    }
    
    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;
    }
    
    if (RELATION_IS_OTHER_TEMP(onerel))
    {
        relation_close(onerel, lmode);
        PopActiveSnapshot();
        CommitTransactionCommand();
        return false;
    }
    
    if (onerel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
    {
        relation_close(onerel, lmode);
        PopActiveSnapshot();
        CommitTransactionCommand();
        
        return true;
    }
    
    onerelid = onerel->rd_lockInfo.lockRelId;
    LockRelationIdForSession(&onerelid, lmode);
    
    if (!(options & VACOPT_SKIPTOAST) && !(options & VACOPT_FULL))
        toast_relid = onerel->rd_rel->reltoastrelid;
    else
        toast_relid = InvalidOid;
    
    GetUserIdAndSecContext(&save_userid, &save_sec_context);
    SetUserIdAndSecContext(onerel->rd_rel->relowner,
                           save_sec_context | SECURITY_RESTRICTED_OPERATION);
    save_nestlevel = NewGUCNestLevel();
    
    if (options & VACOPT_FULL)
    {
        int         cluster_options = 0;
        
        //在vacuuming前关闭relation,持有锁直至commit
        relation_close(onerel, NoLock);
        onerel = NULL;
        if ((options & VACOPT_VERBOSE) != 0)
            cluster_options |= CLUOPT_VERBOSE;
        
        //VACUUM FULL现在是CLUSTER的一个变体,详细请查看cluster.c
        cluster_rel(relid, InvalidOid, cluster_options);
    }
    else
        heap_vacuum_rel(onerel, options, params, vac_strategy);
    
    //回滚索引函数修改的GUC参数
    AtEOXact_GUC(false, save_nestlevel);
    
    //恢复userid和安全上下文
    SetUserIdAndSecContext(save_userid, save_sec_context);
    
    //所有的工作已完成,但持有锁,直到提交
    if (onerel)
        relation_close(onerel, NoLock);
    
    PopActiveSnapshot();
    CommitTransactionCommand();
    
    if (toast_relid != InvalidOid)
        vacuum_rel(toast_relid, NULL, options, params);
    
    UnlockRelationIdForSession(&onerelid, lmode);
    
    //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

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 资料下载
  • 历年真题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯