postgresql源码学习(53)—— vacuum②-lazy vacuum之heap_vacuum_rel函数

news2024/11/13 22:34:58

一、 table_relation_vacuum函数

1. 函数定义

      前篇最后(https://blog.csdn.net/Hehuyi_In/article/details/128749517),我们提到了table_relation_vacuum函数(tableam.h文件),本篇继续学习。

       如前面所说,手动和autovacuum触发的vacuum操作均会走到该函数,需要对表加4级锁。该函数针对lazy vacuum,因此vacuum full,CLUSTER,ANALYZE操作不会走到它。

static inline void
table_relation_vacuum(Relation rel, struct VacuumParams *params,
                      BufferAccessStrategy bstrategy)
{
    rel->rd_tableam->relation_vacuum(rel, params, bstrategy);
}

       这里遇到一个问题,relation_vacuum实际不是一个函数,源码文件中也没找到它的内容,无法再看到下层函数,因此这里借助gdb跟踪一把。

2. 下层函数追踪

b table_relation_vacuum

       可以看到后面实际是调用了heap_vacuum_rel函数(vacuumlazy.c文件)。兜兜转转半天,终于见到了lazy vacuum函数的庐山真面目。但在学习它之前,还是先来看看其中几个预备知识点,避免一脸懵逼。

二、 准备知识

1. TransactionIdPrecedesOrEquals函数

这个老朋友之前学习过,用来比较哪个事务id更旧,原理参考:https://blog.csdn.net/Hehuyi_In/article/details/102869893

/*
 * TransactionIdPrecedesOrEquals --- is id1 logically <= id2?
 */
bool
TransactionIdPrecedesOrEquals(TransactionId id1, TransactionId id2)
{
    int32       diff;

    if (!TransactionIdIsNormal(id1) || !TransactionIdIsNormal(id2))
        return (id1 <= id2);

    diff = (int32) (id1 - id2);
    return (diff <= 0);
}

2. LVRelState结构体

LV指的是Lazy Vacuum,从这个名字可以猜测是与表状态相关的结构体。

typedef struct LVRelState
{
    /* Target heap relation and its indexes */
    Relation    rel;
    Relation   *indrels;
    int         nindexes;

    /* Wraparound failsafe has been triggered? */
    bool        failsafe_active;
    /* Consider index vacuuming bypass optimization? */
    bool        consider_bypass_optimization;

    /* Doing index vacuuming, index cleanup, rel truncation? */
    bool        do_index_vacuuming;
    bool        do_index_cleanup;
    bool        do_rel_truncate;

    /* Buffer access strategy and parallel state */
    BufferAccessStrategy bstrategy;
    LVParallelState *lps;

    /* Statistics from pg_class when we start out */
    BlockNumber old_rel_pages;  /* previous value of pg_class.relpages */
    double      old_live_tuples;    /* previous value of pg_class.reltuples */
    /* rel's initial relfrozenxid and relminmxid */
    TransactionId relfrozenxid;
    MultiXactId relminmxid;

    /* VACUUM operation's cutoff for pruning */
    TransactionId OldestXmin;
    /* VACUUM operation's cutoff for freezing XIDs and MultiXactIds */
    TransactionId FreezeLimit;
    MultiXactId MultiXactCutoff;

    /* Error reporting state */
    char       *relnamespace;
    char       *relname;
    char       *indname;
    BlockNumber blkno;          /* used only for heap operations */
    OffsetNumber offnum;        /* used only for heap operations */
    VacErrPhase phase;

    /*
     * State managed by lazy_scan_heap() follows
     */
    LVDeadTuples *dead_tuples;  /* items to vacuum from indexes */
    BlockNumber rel_pages;      /* total number of pages */
    BlockNumber scanned_pages;  /* number of pages we examined */
    BlockNumber pinskipped_pages;   /* # of pages skipped due to a pin */
    BlockNumber frozenskipped_pages;    /* # of frozen pages we skipped */
    BlockNumber tupcount_pages; /* pages whose tuples we counted */
    BlockNumber pages_removed;  /* pages remove by truncation */
    BlockNumber lpdead_item_pages;  /* # pages with LP_DEAD items */
    BlockNumber nonempty_pages; /* actually, last nonempty page + 1 */

    /* Statistics output by us, for table */
    double      new_rel_tuples; /* new estimated total # of tuples */
    double      new_live_tuples;    /* new estimated total # of live tuples */
    /* Statistics output by index AMs */
    IndexBulkDeleteResult **indstats;

    /* Instrumentation counters */
    int         num_index_scans;
    int64       tuples_deleted; /* # deleted from table */
    int64       lpdead_items;   /* # deleted from indexes */
    int64       new_dead_tuples;    /* new estimated total # of dead items in
                                     * table */
    int64       num_tuples;     /* total number of nonremovable tuples */
    int64       live_tuples;    /* live tuples (reltuples estimate) */
} LVRelState;

三、 heap_vacuum_rel函数

       根据注释,该函数负责vacuum单个堆表、清理其索引、更新relpages,reltuples的统计信息。进入此函数前,我们已经完成了事务开启以及对应表的4级锁获取。

/*
 *  heap_vacuum_rel() -- perform VACUUM for one heap relation
 *
 *      This routine vacuums a single heap, cleans out its indexes, and
 *      updates its relpages and reltuples statistics.
 *
 *      At entry, we have already established a transaction and opened
 *      and locked the relation.
 */
void
heap_vacuum_rel(Relation rel, VacuumParams *params,
                BufferAccessStrategy bstrategy)
{
    LVRelState *vacrel;
    PGRUsage    ru0;
    TimestampTz starttime = 0;
    WalUsage    walusage_start = pgWalUsage;
    WalUsage    walusage = {0, 0, 0};
    long        secs;
    int         usecs;
    double      read_rate,
                write_rate;
    bool        aggressive;     /* should we scan all unfrozen pages? 是否应该扫描所有未冻结页? */
    bool        scanned_all_unfrozen;   /* actually scanned all such pages? 是否实际扫描了所有未冻结页? */
    char      **indnames = NULL;
    TransactionId xidFullScanLimit;
    MultiXactId mxactFullScanLimit;
    BlockNumber new_rel_pages;
    BlockNumber new_rel_allvisible;
    double      new_live_tuples;
    TransactionId new_frozen_xid;
    MultiXactId new_min_multi;
    ErrorContextCallback errcallback;
    PgStat_Counter startreadtime = 0;
    PgStat_Counter startwritetime = 0;
    TransactionId OldestXmin;
    TransactionId FreezeLimit;
    MultiXactId MultiXactCutoff;
…

       首先根据输入的freeze参数,计算并赋值给各类限制值变量(带&的都是),用于下面判断是否采取迫切模式(aggressive)。根据前面的注释,aggressive=true则需要扫描所有未冻结页。冻结相关参考:postgresql_internals-14 学习笔记(三)冻结、rebuild_Hehuyi_In的博客-CSDN博客

 /* 根据输入的freeze参数,计算各类限制值(带&的都是),用于下面判断是否采取迫切(aggressive)清理 */
    vacuum_set_xid_limits(rel,
                          params->freeze_min_age,
                          params->freeze_table_age,
                          params->multixact_freeze_min_age,
                          params->multixact_freeze_table_age,
                          &OldestXmin, &FreezeLimit, &xidFullScanLimit,
                          &MultiXactCutoff, &mxactFullScanLimit);

    /*     
      * 如果表的relfrozenxid <= xidFullScanLimit(表中最新xid- vacuum_freeze_table_age),则触发aggressive scan,multiXid类似;如果设置了DISABLE_PAGE_SKIPPING(禁用跳过页),则也触发aggressive scan。
     */
    aggressive = TransactionIdPrecedesOrEquals(rel->rd_rel->relfrozenxid,
                                               xidFullScanLimit);
    aggressive |= MultiXactIdPrecedesOrEquals(rel->rd_rel->relminmxid,
                                              mxactFullScanLimit);
    if (params->options & VACOPT_DISABLE_PAGE_SKIPPING)
        aggressive = true;

初始化vacrel变量并根据各类option设置其字段初始值,各字段含义参考LVRelState结构体定义。

vacrel = (LVRelState *) palloc0(sizeof(LVRelState));

    /* Set up high level stuff about rel */
    vacrel->rel = rel;
    /* 打开表索引,返回索引名和数量 */
    vac_open_indexes(vacrel->rel, RowExclusiveLock, &vacrel->nindexes,
                     &vacrel->indrels);
    vacrel->failsafe_active = false;
    vacrel->consider_bypass_optimization = true;

    /*
     * The index_cleanup param either disables index vacuuming and cleanup or
     * forces it to go ahead when we would otherwise apply the index bypass
     * optimization.  The default is 'auto', which leaves the final decision
     * up to lazy_vacuum().
     *
     * The truncate param allows user to avoid attempting relation truncation,
     * though it can't force truncation to happen.
     */
    Assert(params->index_cleanup != VACOPTVALUE_UNSPECIFIED);
    Assert(params->truncate != VACOPTVALUE_UNSPECIFIED &&
           params->truncate != VACOPTVALUE_AUTO);
    vacrel->do_index_vacuuming = true;
    vacrel->do_index_cleanup = true;
    vacrel->do_rel_truncate = (params->truncate != VACOPTVALUE_DISABLED);
    if (params->index_cleanup == VACOPTVALUE_DISABLED)
    {
        /* Force disable index vacuuming up-front */
        vacrel->do_index_vacuuming = false;
        vacrel->do_index_cleanup = false;
    }
    else if (params->index_cleanup == VACOPTVALUE_ENABLED)
    {
        /* Force index vacuuming.  Note that failsafe can still bypass. */
        vacrel->consider_bypass_optimization = false;
    }
    else
    {
        /* Default/auto, make all decisions dynamically */
        Assert(params->index_cleanup == VACOPTVALUE_AUTO);
    }

    vacrel->bstrategy = bstrategy;
    vacrel->old_rel_pages = rel->rd_rel->relpages;
    vacrel->old_live_tuples = rel->rd_rel->reltuples;
    vacrel->relfrozenxid = rel->rd_rel->relfrozenxid;
    vacrel->relminmxid = rel->rd_rel->relminmxid;

    /* Set cutoffs for entire VACUUM */
    vacrel->OldestXmin = OldestXmin;
    vacrel->FreezeLimit = FreezeLimit;
    vacrel->MultiXactCutoff = MultiXactCutoff;

    vacrel->relnamespace = get_namespace_name(RelationGetNamespace(rel));
    vacrel->relname = pstrdup(RelationGetRelationName(rel));
    vacrel->indname = NULL;
    vacrel->phase = VACUUM_ERRCB_PHASE_UNKNOWN;
…

       lazy_scan_heaplazy vacuum的核心函数,该函数将首先扫描表(会用到vm文件),找到无效的元组和具有空闲空间的page,然后计算表的有效元组数,最后执行表和索引的清理操作。

    /* Do the vacuuming,核心函数 */
    lazy_scan_heap(vacrel, params, aggressive);
  • 关闭表索引
  • 计算实际是否扫描了所有未冻结页(aggressive模式),并设置scanned_all_unfrozen的值
  • lazy_truncate_heap进行文件末尾的页截断(可选操作),这部分空间可以释放回操作系统。注意这个函数会短暂加8级锁,有可能影响业务
  • ConditionalLockRelation(vacrel->rel, AccessExclusiveLock)
  • 更新pg_class中的统计信息
  • 清理vacrel中的索引统计信息及索引名
/* Done with indexes,关闭表索引 */
    vac_close_indexes(vacrel->nindexes, vacrel->indrels, NoLock);

    /*
     * Compute whether we actually scanned the all unfrozen pages. If we did,
     * we can adjust relfrozenxid and relminmxid.
     *
     * NB: We need to check this before truncating the relation, because that
     * will change ->rel_pages.
     */
    if ((vacrel->scanned_pages + vacrel->frozenskipped_pages)
        < vacrel->rel_pages)
    {
        Assert(!aggressive);
        scanned_all_unfrozen = false;
    }
    else
        scanned_all_unfrozen = true;

    /*
     * Optionally truncate the relation.尝试truncate文件末的页
     */
    if (should_attempt_truncation(vacrel))
    {
        /*
         * Update error traceback information.  This is the last phase during
         * which we add context information to errors, so we don't need to
         * revert to the previous phase.
         */
        update_vacuum_error_info(vacrel, NULL, VACUUM_ERRCB_PHASE_TRUNCATE,
                                 vacrel->nonempty_pages,
                                 InvalidOffsetNumber);
        lazy_truncate_heap(vacrel);
    }

    /*
     * Update statistics in pg_class.
     */
    new_rel_pages = vacrel->rel_pages;
    new_live_tuples = vacrel->new_live_tuples;

    visibilitymap_count(rel, &new_rel_allvisible, NULL);
    if (new_rel_allvisible > new_rel_pages)
        new_rel_allvisible = new_rel_pages;

    new_frozen_xid = scanned_all_unfrozen ? FreezeLimit : InvalidTransactionId;
    new_min_multi = scanned_all_unfrozen ? MultiXactCutoff : InvalidMultiXactId;

    vac_update_relstats(rel,
                        new_rel_pages,
                        new_live_tuples,
                        new_rel_allvisible,
                        vacrel->nindexes > 0,
                        new_frozen_xid,
                        new_min_multi,
                        false);
    …
    /* Cleanup index statistics and index names */
    for (int i = 0; i < vacrel->nindexes; i++)
    {
        if (vacrel->indstats[i])
            pfree(vacrel->indstats[i]);

        if (indnames && indnames[i])
            pfree(indnames[i]);
    }
}

如你所见,我们又掉进了新的坑里——lazy_scan_heap,下一篇继续研究研究这个函数~

参考:

PostgreSQL数据库内核分析》

PostgreSQL 源码解读(128)- MVCC#12(vacuum过程-heap_vacuum_rel函数)_ITPUB博客

http://blog.itpub.net/6906/viewspace-2564641/

Postgresql Freezing 实现原理_13446560的技术博客_51CTO博客

https://www.pudn.com/news/6277722b517cd20ea491bf39.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/177126.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

人大金仓数据库对象访问权限

数据库的表、索引、视图等&#xff0c;在数据库中的一切都可以称为数据库对象。 对象分为以下两类 模式&#xff08;SCHEMA&#xff09;对象&#xff1a;可视为一个表的集合&#xff0c;可以理解为一个存储目录&#xff0c;包含视图、索引、数据类型、函数和操作符等。非模式…

AcWing1229.日期问题——学习笔记

目录 题目 代码 AC结果 思路&#xff1a; 一、获取数据 二、验证日期合法性 三、去重 四、排序 五、主方法中调用&输出 题目 1229. 日期问题 - AcWing题库https://www.acwing.com/problem/content/description/1231/ 代码 import java.util.Scanner;public class…

XILINX FPGA OV5640 摄像头驱动(一)

影像行业是一个值得深耕的方向&#xff0c;废话不多说 先看输入和输出 输入是光照&#xff0c;输出是光照的数字信号 image area&#xff1a;说的是感光矩阵&#xff0c;CMOS图像传感器的最核心部分&#xff0c;接收光照产生电信号的部分。决定了图像质量的好坏 矩阵就会行列…

MyBatisPlus笔记

一、MyBatisPlus概述 MyBatisPlus&#xff08;简称 MP&#xff09;是一个MyBatis的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 MyBatis-Plus可以节省我们大量工作时间&#xff0c;所有的CRUD代码它都可以自动化完成&…

leetcode1143 最长公共子序列

题目 给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&#xff1a;它是由原字符串在不改变字符的相对顺序的情况下删除某些字符&#xff08;…

因果诊断原理

因果分析在近十来年逐渐倍受关注&#xff0c;其提供了解释因子间因果性的定量分析工具&#xff0c;广泛用于数据分析领域&#xff0c;同时也就用决策分析、作用预估等反事实因果推理中。本文首先对比了因果性和相关性的关系&#xff0c;之后确定因果关系的基本方法&#xff0c;…

博客搭建教程1-Archlinux环境配置

文章目录1 前言2 archlinux镜像下载3 archlinux安装1 前言 这个教程主要讲解linux环境下博客的搭建&#xff0c;这里的linux系统选择archlinux&#xff0c;博客的框架基于hexo框架。 参考博客&#xff1a; 1、ArchLinux安装教程 2、Archlinux2022年7月镜像 手把手安装教程 UE…

MySQL进阶——存储过程

MySQL 存储过程 1、简介 大多数 SQL 语句都是针对一个或多个表的单条语句。并非所有的操作都那么简单。经常会有一个完整的操作需要多条语句才能完成。 存储过程简单来说&#xff0c;就是为以后的使用而保存的一条或多条 MySQL 语句的集合。可将其视为批处理文件。虽然他们的…

【Spring(八)】带你打通Spring的注解开发

文章目录注解开发注解开发定义bean纯注解开发注解开发bean作用范围与生命周期管理注解开发依赖注入注解开发管理第三方bean注解开发实现为第三方bean注入资源总结注解开发 Spring的配置我们已经告一段落了&#xff0c;那接下来我们就要发挥Spring的强项了&#xff1a;简化开发&…

MySQL —— 数据库基础

目录 一、数据库的基本概念 1. 什么是数据库 2. 主流的数据库 二、基本使用 1. 连接服务器 2. 服务器管理 3. 服务器、数据库、表关系 4. 使用案例 5. 数据库的存储逻辑 三、MySQL架构 四、SQL分类 五、存储引擎 1. 存储引擎 2. 查看存储引擎 3. 存储引擎对比 …

Elasticsearch 这篇还不够吗

系列文章目录 文章目录系列文章目录一、概述1. ES 的基本概念2. ES 和关系型数据库的对比二、环境准备1. linux 下单机安装三、入门操作1. 创建索引2. 写入文档3. 根据id搜索文档4. 根据一般字段搜索文档5. 根据文本字段搜索文档四、ES 客户端实战1. Spring Data Elasticsearch…

学习shell与shell编程 vi与vim

Linux配置文件都是以ASCII的纯文本形式存在。 为什么学习vi 1)UnixLike系统都会内置vi文本编辑器&#xff0c;其他的文本编辑器则不一定存在 2)许多软件的编辑接口都会主动调用vi 3)vi具有程序编辑的能力&#xff0c;可以主动以字体颜色辨别语法的正确性 4)程序简单&#…

webgl纹理贴图机制

文章目录前言纹理图片大小规范纹理坐标系统贴图流程JavaScript部分齐次坐标—uv坐标数据准备加载外部纹理图像纹理配置加载着色器部分顶点着色器片元着色器完整示例使用多张纹理着色器接受两个纹理单元封装纹理配置赋值函数完整示例总结前言 在计算机图形学中&#xff0c;为了…

HTML+CSS+JS制作炫酷【烟花特效】

文章目录制作炫酷烟花特效一、普通烟花(分散形)HTML代码CSS代码JS代码二、圆形烟花HTML代码CSS代码JS代码三、爱心形烟花HTML代码CSS代码JS代码四、源码获取在线下载制作炫酷烟花特效 &#x1f4a1;本篇内容使用htmlcssjs制作鼠标点击出现烟花效果&#xff0c;分别介绍了分散型…

python-测试代码

1. 测试函数get_name.pydef combination(first, last):将姓名组合在一起name first lastreturn name.title()hello_world.pyfrom get_name import combinationprint("Enter q to quit!") while True:first input(Please input your first name: )if first q:b…

理光Aficio MP C2500扫描到文件夹设置方法

首先在需要接收扫描文件的电脑上设置共享文件夹。 注&#xff1a; &#xff08;1&#xff09;文件夹的名字最好简单一点&#xff0c;比如&#xff1a;scan、123等等&#xff1b; &#xff08;2&#xff09;文件夹的共享权限最好能设置为最大&#xff08;WindowsXP、Windows200…

Future、CompletableFuture概述

1.同步和异步 &#xff08;1&#xff09;同步&#xff1a;需要等待结果返回&#xff0c;才能继续运行 &#xff08;2&#xff09;异步&#xff1a;不需要等待结果返回&#xff0c;就能继续运行 &#xff08;3&#xff09;异步设计&#xff1a;多线程可以让方法执行变为异步(比…

第四章必备前端基础知识-第二节3:CSS盒模型和浮动

文章目录一&#xff1a;盒模型&#xff08;1&#xff09;border&#xff08;2&#xff09;padding&#xff08;3&#xff09;margin二&#xff1a;flex布局一&#xff1a;盒模型 盒模型&#xff1a;在HTML中&#xff0c;每个标签&#xff08;或元素&#xff09;相当于是一个盒…

Mybatis和Jpa

这里写目录标题1.Mybatis1.1 JDBC的缺点1.2 Mybatis的整体架构1.3 入门案例1.3.1 问题:无法连接到数据库服务器1.4 动态代理实现Mapper1.5 mybatis-config.xml配置1.5.1 properties属性读取外部资源1.5.2 settings设置1.5.3 typeAliases1.5.4 typeHandlers&#xff08;类型处理…

【Substance Designer】基础操作和节点学习记录

写在前面 这个记录稍微有点杂&#xff0c;大概是庄懂的技术美术入门课(美术向)-直播录屏-第20课和一些基础操作的记录合集吧&#xff01; 补充 学习发现&#xff0c;基础的节点是需要学习和记录的&#xff0c;但是真正用起来还是要多用多练&#xff01;所以这种简单的记录节点…