【 OpenGauss源码学习 —— 列存储(autoanalyze)(二)】

news/2024/7/9 22:57:46 标签: 学习, gaussdb, postgresql, 数据库

列存储(autoanalyze)(二)

  • 概述
  • PgStat_StatTabEntry 结构体
  • pgstat_count_heap_insert 与 pgstat_count_cu_insert 函数
  • CStoreInsert::BatchInsertCommon 函数
  • pgstat_count_cu_update 函数
  • pgstat_count_cu_delete 函数
  • pgstat_count_truncate 函数
  • pgstat_update_heap_dead_tuples 函数
  • 总结

声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 OpenGauss1.1.0 的开源代码和《OpenGauss数据库源码解析》一书以及OpenGauss社区学习文档和一些学习资料

概述

  列存储autoanalyze)是一种数据库自动维护统计信息的功能,它定期分析表中的数据分布和变化情况,以优化查询性能。自动分析通过检查表的内容,确定最佳查询计划,从而帮助数据库优化查询操作,提高查询性能,特别是在大型数据库中。这个过程会定期运行,通常由数据库自动调度,以确保统计信息的准确性和最新性,以便数据库查询优化器可以更好地选择执行计划。这有助于提高数据库的性能和响应速度减少了手动维护统计信息的工作负担
  在上一章中我们学习relation_needs_vacanalyze 函数,其作用是确定一个表是否需要执行自动化的 VACUUM 或 ANALYZE 操作,并返回相应的决策。在relation_needs_vacanalyze 函数中,以下代码的主要目的是根据一些阈值和条件来确定是否需要对表进行自动的 VACUUMANALYZE 操作

if (false == *dovacuum && allowVacuum)
    *dovacuum = ((float4)vactuples > vacthresh);

// 确定表是否需要分析
if (allowAnalyze)
    *doanalyze = ((float4)anltuples > anlthresh);

  以下表格中列举了相关参数及含义:

参 数含 义
vac_base_thresh表的自动 VACUUM 操作的基本阈值,通常是一个整数,表示表中元组数量的下限,低于此下限将触发 VACUUM
anl_base_thresh表的自动 ANALYZE 操作的基本阈值,通常是一个整数,表示表中元组数量的下限,低于此下限将触发 ANALYZE
vac_scale_factorVACUUM 操作的缩放因子,通常是一个浮点数,用于根据表的大小和性能需求调整自动 VACUUM 的触发条件。
anl_scale_factorANALYZE 操作的缩放因子,通常是一个浮点数,用于根据表的大小和性能需求调整自动 ANALYZE 的触发条件。
vacthresh实际的自动 VACUUM 操作阈值,根据基本阈值和缩放因子计算得出。
anlthresh实际的自动 ANALYZE 操作阈值,根据基本阈值和缩放因子计算得出。
vactuples跟踪表的元组数量,用于自动 VACUUM 操作中。
anltuples跟踪表的元组数量,用于自动 ANALYZE 操作中。

那么问题来了,vactuplesanltuples 是如何获取到的呢?。

  观察如下代码:

if (tabentry && (tabentry->changes_since_analyze || tabentry->n_dead_tuples)) {
    anltuples = tabentry->changes_since_analyze;
    vactuples = tabentry->n_dead_tuples;
    AUTOVAC_LOG(DEBUG2, "fetch local stat info: vac \"%s\" changes_since_analyze = %ld  n_dead_tuples = %ld ",
         NameStr(classForm->relname), tabentry->changes_since_analyze, tabentry->n_dead_tuples);
}
if (avwentry && (avwentry->changes_since_analyze || avwentry->n_dead_tuples)) {
    anltuples = avwentry->changes_since_analyze;
    vactuples = avwentry->n_dead_tuples;
    AUTOVAC_LOG(DEBUG2, "fetch global stat info: vac \"%s\" changes_since_analyze = %ld  n_dead_tuples = %ld ",
         NameStr(classForm->relname), avwentry->changes_since_analyze, avwentry->n_dead_tuples);
 }

  这段代码用于从统计信息中获取表的相关统计数据changes_since_analyzen_dead_tuples),并将这些数据分别赋值给 anltuplesvactuples,以便后续决定是否执行自动 ANALYZE自动 VACUUM 操作。那么 tabentry 又是什么呢?

PgStat_StatTabEntry 结构体

  PgStat_StatTabEntry 结构体,用于存储数据库中表的统计信息。源码如下:(路径:src/include/pgstat.h

typedef struct PgStat_StatTabEntry {
    PgStat_StatTabKey tablekey;   /* 表的统计信息的唯一标识符 */

    PgStat_Counter numscans;     /* 表的扫描次数 */

    PgStat_Counter tuples_returned;   /* 从表中返回的元组数 */
    PgStat_Counter tuples_fetched;    /* 从表中提取的元组数 */

    PgStat_Counter tuples_inserted;   /* 插入到表中的元组数 */
    PgStat_Counter tuples_updated;    /* 更新表中的元组数 */
    PgStat_Counter tuples_deleted;    /* 从表中删除的元组数 */
    PgStat_Counter tuples_hot_updated;    /* 热更新的元组数(在没有引起索引更新的情况下更新的元组数) */

    PgStat_Counter n_live_tuples;   /* 存活的元组数(未被标记为死亡的元组数) */
    PgStat_Counter n_dead_tuples;   /* 死亡的元组数(标记为死亡但尚未被清除的元组数) */
    PgStat_Counter changes_since_analyze; /* 从上次分析以来发生的变化次数 */

    PgStat_Counter blocks_fetched;  /* 从磁盘读取的块数 */
    PgStat_Counter blocks_hit;  /* 从缓存中读取的块数 */

    PgStat_Counter cu_mem_hit;  /* 连续内存中的命中次数 */
    PgStat_Counter cu_hdd_sync; /* 硬盘同步的次数 */
    PgStat_Counter cu_hdd_asyn; /* 硬盘异步的次数 */

    TimestampTz vacuum_timestamp;   /* 用户发起的 VACUUM 的时间戳 */
    PgStat_Counter vacuum_count;    /* 用户发起的 VACUUM 的计数 */

    TimestampTz autovac_vacuum_timestamp; /* 自动 VACUUM 的时间戳 */
    PgStat_Counter autovac_vacuum_count;  /* 自动 VACUUM 的计数 */

    TimestampTz analyze_timestamp;   /* 用户发起的分析的时间戳 */
    PgStat_Counter analyze_count;    /* 用户发起的分析的计数 */

    TimestampTz autovac_analyze_timestamp;   /* 自动分析的时间戳 */
    PgStat_Counter autovac_analyze_count;    /* 自动分析的计数 */

    TimestampTz data_changed_timestamp;  /* 数据变更的时间戳(例如,插入、删除、更新操作的时间戳) */

    uint64 autovac_status;  /* 自动 VACUUM 的状态信息 */
} PgStat_StatTabEntry;

  PgStat_StatTabEntry 结构体用于存储数据库每个表的统计信息,包括表的扫描次数元组的增删改查数量块的读取次数硬盘访问情况时间戳等信息,用于监控分析表的使用情况和性能表现,特别是在自动 VACUUM 和分析过程中提供了关键的统计数据

pgstat_count_heap_insert 与 pgstat_count_cu_insert 函数

  在了解了以上逻辑后,我们知道每次操作表后会更改统计信息,那么是怎么修改的呢?我们首先来从 pgstat_count_heap_insert 函数开始看起吧。
  pgstat_count_heap_insert 函数用于在数据库统计表中插入新元组的操作。它追踪并记录了在当前事务嵌套级别下插入的元组数量,以便进行性能监控统计分析
  pgstat_count_heap_insert 函数源码如下所示:(路径:src/gausskernel/process/postmaster/pgstat.cpp

/*
 * pgstat_count_heap_insert - count a tuple insertion of n tuples
 * 统计插入 n 条元组的操作次数
 */
void pgstat_count_heap_insert(Relation rel, int n)
{
    PgStat_TableStatus* pgstat_info = rel->pgstat_info;

    // 检查是否需要进行统计
    if (pgstat_info != NULL) {
        /* We have to log the effect at the proper transactional level */
        // 确定当前嵌套事务级别
        int nest_level = GetCurrentTransactionNestLevel();

        // 如果当前事务级别和记录的事务级别不一致,需要添加新的事务级别信息
        if (pgstat_info->trans == NULL || pgstat_info->trans->nest_level != nest_level)
            add_tabstat_xact_level(pgstat_info, nest_level);

        // 更新当前事务级别下插入的元组数量
        pgstat_info->trans->tuples_inserted += n;
    }
}

  此外,pgstat_count_cu_insert 函数是一个宏定义,用于将 pgstat_count_cu_insert 映射为 pgstat_count_heap_insert 函数的调用,其作用是用于统计某个表中的元组插入操作的次数
  pgstat_count_cu_insert 宏定义如下所示:(路径:src/include/pgstat.h

#define pgstat_count_cu_insert(rel, n)    \
    do {                                  \
        pgstat_count_heap_insert(rel, n); \
    } while (0)

CStoreInsert::BatchInsertCommon 函数

  了解了 pgstat_count_heap_insert 函数和 pgstat_count_cu_insert 函数的作用,那这两个函数是在哪里调用的呢?答案是在 CStoreInsert::BatchInsertCommon 函数中进行调用的,该函数用于执行批量插入操作到列存储表,其功能包括创建列更新单元CU)、统计插入或更新操作次数以及批量插入索引表中的数据。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_insert.cpp

void CStoreInsert::BatchInsertCommon(bulkload_rows* batchRowPtr, int options)
{
    // 检查参数有效性,若为空或行数为0则直接返回
    if (unlikely(batchRowPtr == NULL || batchRowPtr->m_rows_curnum == 0))
        return;

    int attno = m_relation->rd_rel->relnatts;
    int col = 0;
    Assert(attno == batchRowPtr->m_attr_num); // 断言表的列数和批处理行的列数相等

    CHECK_FOR_INTERRUPTS(); // 检查是否收到中断信号

    /* 步骤 1: 创建列更新单元(CU)和 CU 描述(CUDesc) */
    for (col = 0; col < attno; ++col) {
        // 如果列没有被删除,则创建相应的 CU 和 CUDesc
        if (!m_relation->rd_att->attrs[col]->attisdropped) {
            m_cuPPtr[col] = FormCU(col, batchRowPtr, m_cuDescPPtr[col]);
            m_cuCmprsOptions[col].m_sampling_fihished = true;
        }
    }

    // 根据是否为更新操作,统计 CU 插入或更新的次数
    if (m_isUpdate)
        pgstat_count_cu_update(m_relation, batchRowPtr->m_rows_curnum);
    else
        pgstat_count_cu_insert(m_relation, batchRowPtr->m_rows_curnum);

    /*
     * 步骤 2:
     * a) 分配 CUID(列更新单元ID)和 CUPointer(列更新单元指针)
     * b) 写入 CU 和 CUDesc
     */
    SaveAll(options);

    /* 步骤 3: 批量插入索引表 */
    if (m_relation->rd_att->attrs[0]->attisdropped) {
        int fstColIdx = CStoreGetfstColIdx(m_relation);
        InsertIdxTableIfNeed(batchRowPtr, m_cuDescPPtr[fstColIdx]->cu_id);
    } else
        InsertIdxTableIfNeed(batchRowPtr, m_cuDescPPtr[0]->cu_id);
}

  其中,参数 m_isUpdate 用于指示是否执行更新操作。如果 m_isUpdate 为真(true),则表示执行更新操作;如果 m_isUpdate 为假(false),则表示执行插入操作。在函数的后续部分,根据 m_isUpdate 的值来统计插入或更新的次数

pgstat_count_cu_update 函数

  在 CStoreInsert::BatchInsertCommon 函数中还调用了 pgstat_count_cu_update 函数,其主要功能是在统计信息中记录列存(CStore)DFS 表的更新操作的数量。它首先获取与给定关系(Relation)关联的统计信息,然后检查是否需要在适当的事务级别记录操作效果。最后,它将更新的数量添加到统计信息中,以反映列存/DFS表的更新操作情况。函数源码如下所示:(路径:src/gausskernel/process/postmaster/pgstat.cpp

/*
 * pgstat_count_cu/dfs_update - count a cstore/dfs update
 * pgstat_count_cu/dfs_update - 记录列存/DFS更新操作
 */
void pgstat_count_cu_update(Relation rel, int n)
{
    // 获取与关系关联的统计信息
    PgStat_TableStatus* pgstat_info = rel->pgstat_info;

    // 如果存在统计信息
    if (pgstat_info != NULL) {
        // 需要在适当的事务级别记录操作效果
        int nest_level = GetCurrentTransactionNestLevel();

        // 如果没有事务信息或者事务嵌套级别与当前不匹配,需要添加新的事务级别统计信息
        if (pgstat_info->trans == NULL || pgstat_info->trans->nest_level != nest_level)
            add_tabstat_xact_level(pgstat_info, nest_level);

        // 更新统计信息中的"更新的元组数"字段,表示列存/DFS更新操作的数量
        pgstat_info->trans->tuples_updated += n;
    }
}

pgstat_count_cu_delete 函数

  pgstat_count_cu_delete 函数的主要作用是,根据传入的关系(表)删除的行数(n),统计列存DFS删除操作的数量,并记录在 PgStat_TableStatus 结构中,以便后续的性能统计和监视。函数首先获取表的 PgStat_TableStatus 信息,然后检查是否在当前事务层次中,如果不是,则添加一个新的事务层次。最后,增加已删除元组的数量。函数源码如下所示:(路径:src/gausskernel/process/postmaster/pgstat.cpp
  以下代码定义了两个函数 pgstat_count_cu_deletepgstat_count_dfs_delete,用于统计列存(CStore)分布式文件系统(DFS)删除操作

/*
 * pgstat_count_cu/dfs_delete - 统计列存/DFS删除操作的数量
 */
void pgstat_count_cu_delete(Relation rel, int n)
{
    // 获取表的 PgStat_TableStatus 信息
    PgStat_TableStatus* pgstat_info = rel->pgstat_info;

    if (pgstat_info != NULL) {
        // 需要在正确的事务层次记录操作效果
        int nest_level = GetCurrentTransactionNestLevel();

        // 如果当前事务层次与记录的事务层次不同,添加新的事务层次
        if (pgstat_info->trans == NULL || pgstat_info->trans->nest_level != nest_level)
            add_tabstat_xact_level(pgstat_info, nest_level);

        // 增加已删除元组的数量
        pgstat_info->trans->tuples_deleted += n;
    }
}

pgstat_count_truncate 函数

  pgstat_count_truncate 函数用于在执行表的截断操作后更新元组计数器。其主要作用是:根据传入的关系(表),在执行表的截断操作后,重置元组计数器,以便反映截断操作的影响。函数首先获取表的 PgStat_TableStatus 信息,然后检查是否在当前事务层次中,如果不是,则添加一个新的事务层次。接着,保存在截断前的计数器值,然后将插入更新删除的计数器重置为0,以便后续的性能统计和监视。
  pgstat_count_truncate 函数源码如下所示:(路径:src/gausskernel/process/postmaster/pgstat.cpp

/*
 * pgstat_count_truncate - 由于截断而更新元组计数器
 */
void pgstat_count_truncate(Relation rel)
{
    // 获取表的 PgStat_TableStatus 信息
    PgStat_TableStatus* pgstat_info = rel->pgstat_info;

    if (pgstat_info != NULL) {
        // 需要在正确的事务层次记录操作效果
        int nest_level = GetCurrentTransactionNestLevel();

        // 如果当前事务层次与记录的事务层次不同,添加新的事务层次
        if (pgstat_info->trans == NULL || pgstat_info->trans->nest_level != nest_level)
            add_tabstat_xact_level(pgstat_info, nest_level);

        // 保存截断前的计数器值
        pgstat_truncate_save_counters(pgstat_info->trans);

        // 将插入、更新和删除的计数器重置为0
        pgstat_info->trans->tuples_inserted = 0;
        pgstat_info->trans->tuples_updated = 0;
        pgstat_info->trans->tuples_deleted = 0;
    }
}

pgstat_update_heap_dead_tuples 函数

  pgstat_update_heap_dead_tuples 函数的主要作用是:根据传入的关系(表)和增量值更新死元组的计数。函数首先获取表的 PgStat_TableStatus 信息,然后将增量值减去 t_delta_dead_tuples,以表示死元组的恢复。这个函数用于非事务性的 “delta” 死元组的计数,不会影响事务性状态,而是直接更新表的计数器

/*
 * pgstat_update_heap_dead_tuples - 更新死元组的计数
 *
 * 这个函数的语义是我们正在报告非事务性的 "delta" 死元组的恢复;
 * 因此,t_delta_dead_tuples 减少而不是增加,并且更改直接进入每个表的计数器,
 * 而不是进入事务性状态。
 */
void pgstat_update_heap_dead_tuples(Relation rel, int delta)
{
    // 获取表的 PgStat_TableStatus 信息
    PgStat_TableStatus* pgstat_info = rel->pgstat_info;

    if (pgstat_info != NULL)
        // 减少 delta 值,表示死元组的恢复
        pgstat_info->t_counts.t_delta_dead_tuples -= delta;
}

总结

  以上这些函数用于数据库性能统计和监控,可以跟踪指定关系(表)的插入更新删除截断CU(Chunk Update)等操作,以记录和报告这些操作的频率数量,帮助数据库管理员识别性能问题并监视数据库的活动。下表对以上函数进行了总结:

函 数作 用
pgstat_count_heap_insert用于统计在指定关系(表)中插入了多少行数据。
pgstat_count_heap_update用于统计在指定关系(表)中进行了多少次更新操作。
pgstat_count_heap_delete用于统计在指定关系(表)中进行了多少次删除操作。
pgstat_count_truncate用于统计在指定关系(表)上执行了多少次截断操作。
pgstat_update_heap_dead_tuples用于更新关系(表)中的死元组数量统计信息
pgstat_count_cu_update用于统计在指定关系(表)的 CU(Chunk Update)上进行了多少次更新操作。
pgstat_count_cu_delete用于统计在指定关系(表)的 CU 上进行了多少次删除操作。

http://www.niftyadmin.cn/n/5078221.html

相关文章

css吸顶特效(elementui vue3官网)

效果如图&#xff1a;当浏览器滚轮在最上面的时候 没什么区别。当鼠标滚轮超出最上面高度时会有这种粒子感。吸顶遮盖下面内容 首先要 明确 css 基础属性 position: sticky;的用法。再了解 background-image: radial-gradient(transparent 1px, #fff 1px); background-size: …

转守为攻,亚马逊云换帅背后的战略转向

点击关注 文&#xff5c;刘雨琦 一则人事任命&#xff0c;揭开了亚马逊云在大中华区反击战的序幕。 10月9日&#xff0c;亚马逊云科技全球销售、市场和服务高级副总裁 Matt Garman 宣布了大中华区领导人变更任命&#xff0c;储瑞松将接替张文翊担任该职位&#xff0c;继续带领…

vue-6

一、声明式导航-导航链接 1.需求 实现导航高亮效果 如果使用a标签进行跳转的话&#xff0c;需要给当前跳转的导航加样式&#xff0c;同时要移除上一个a标签的样式&#xff0c;太麻烦&#xff01;&#xff01;&#xff01; 2.解决方案 vue-router 提供了一个全局组件 router…

基于SSM线上课程管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

注释那些事

这可是一个又爱又恨、又有点双标的话题哦&#xff01;就像吃巧克力一样&#xff0c;有时候甜蜜满溢&#xff0c;有时候又有点腻。那么&#xff0c;到底是怎么回事呢&#xff1f; 你知道吗&#xff0c;我发现程序员写注释就像在恋爱中的短信一样&#xff0c;有时候真的是甜蜜无…

前端js八股文大全

一、js的数据类型 值类型(基本类型)&#xff1a;数字(Number)、字符串&#xff08;String&#xff09;、布尔(Boolean)、对空&#xff08;Null&#xff09;、未定义&#xff08;Undefined&#xff09;、Symbol,大数值类型(BigInt) 引用数据类型&#xff1a;对象(Object)、数组…

嵌入式养成计划-38----C++--匿名对象--友元--常成员函数和常对象--运算符重载

八十七、匿名对象 概念&#xff1a;没有名字对象格式 &#xff1a;类名&#xff08;&#xff09;;作用 用匿名对象给有名对象初始化的用匿名对象给对象数组初始化的匿名对象作为函数实参使用 示例 : #include <iostream> using namespace std; class Dog { private:s…

前端已死,能转行干什么?

我前几天招人&#xff0c;前后端各招一个人。 后端一天大概60多个投简历的。 前端岗位发出去&#xff0c;我吃了个饭&#xff0c;1小时回来 收到300多份简历…… 是一位HR回复的前端卷到什么程度的回答&#xff01; 下面我们来看两组官方纰漏的数据&#xff1a; 2023届全国高…