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

2023-10-25 06:52

本文主要是介绍【 OpenGauss源码学习 —— 列存储(update_pages_and_tuples_pgclass)】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

列存储(update_pages_and_tuples_pgclass)

  • 概述
  • update_pages_and_tuples_pgclass 函数
    • ReceivePageAndTuple 函数
    • estimate_cstore_blocks 函数
      • get_attavgwidth 函数
      • get_typavgwidth 函数
    • vac_update_relstats 函数
  • 测试案例

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

概述

  本文所学习的重点依旧围绕列存储展开。

update_pages_and_tuples_pgclass 函数

  update_pages_and_tuples_pgclass 函数的主要功能是在 PostgreSQL 数据库中更新表和索引的统计信息,特别是更新 pg_class 中的关系页面数总行数总死行数等统计数据。它还计算并更新一个表扩展因子,用于查询优化。此函数还负责对表的索引进行统计信息的更新,以帮助查询优化器做出更准确的决策。
  update_pages_and_tuples_pgclass 函数源码如下所示:(路径:src/gausskernel/optimizer/commands/analyze.cpp

/** update_pages_and_tuples_pgclass: 更新用于分析到pg_class的关系页面和元组。** 参数:*	@in onerel: 用于分析或清理的关系*	@in vacstmt: 用于分析或清理命令的语句*	@in attr_cnt: 用于分析的属性计数*	@in vacattrstats: 所有属性的统计结构,或通过分析命令索引的统计结构,用于计算并更新pg_statistic。*	@in hasindex: 是否存在索引用于分析*	@in nindexes: 分析的索引数量*	@in indexdata: 用于分析的每个索引的数据*	@in Irel: 为分析malloc的新索引关系*	@in relpages: 关系页面数*	@in totalrows: 关系的总行数*	@in totaldeadrows: 关系的总死行数*	@in numrows: 样本行数*	@in inh: 是否为继承表进行分析** 返回: 无*/
static void update_pages_and_tuples_pgclass(Relation onerel, VacuumStmt* vacstmt, int attr_cnt,VacAttrStats** vacattrstats, bool hasindex, int nindexes, AnlIndexData* indexdata, Relation* Irel,BlockNumber relpages, double totalrows, double totaldeadrows, int64 numrows, bool inh)
{BlockNumber updrelpages = relpages;/** 当我们从dn1接收pg_class时,我们已在ReceivePageAndTuple函数中更新了pg_class,* 因此,此处仅在数据节点上更新pg_class。*/if (IS_PGXC_DATANODE) {BlockNumber mapCont = 0;/* 更新列存储表的relpages */if (RelationIsColStore(onerel)) {/** 对于CU格式和PAX格式,由于RelationGetNumberOfBlocks返回0,因此我们必须从totalrows生成relpages** 最佳方法是使用所有行(活动和死行),但为了确保向前兼容性,我们只能处理所有行都为死行的情况。*/double allrows = totalrows > 0 ? totalrows : totaldeadrows;/* 如果关系是DFS表,则从HDFS文件获取relpage。 */if (RelationIsPAXFormat(onerel)) {updrelpages = estimate_cstore_blocks(onerel, vacattrstats, attr_cnt, numrows, allrows, true);} else {updrelpages = estimate_cstore_blocks(onerel, vacattrstats, attr_cnt, numrows, allrows, false);}}
#ifdef ENABLE_MULTIPLE_NODESelse if (RelationIsTsStore(onerel)) {updrelpages = estimate_tsstore_blocks(onerel, attr_cnt, totalrows);}
#endif   /* ENABLE_MULTIPLE_NODES */if (RelationIsPartitioned(onerel)) {Relation partRel = NULL;ListCell* partCell = NULL;Partition part = NULL;foreach (partCell, vacstmt->partList) {part = (Partition)lfirst(partCell);partRel = partitionGetRelation(onerel, part);mapCont += visibilitymap_count(onerel, part);releaseDummyRelation(&partRel);}} else {mapCont = visibilitymap_count(onerel, NULL);}Relation classRel = heap_open(RelationRelationId, RowExclusiveLock);vac_update_relstats(onerel, classRel, updrelpages, totalrows, mapCont, hasindex, InvalidTransactionId);heap_close(classRel, RowExclusiveLock);}/* 估算表扩展因子 */double table_factor = 1.0;/* 使用GUC参数PARAM_PATH_OPTIMIZATION计算table_factor */if (ENABLE_SQL_BETA_FEATURE(PARAM_PATH_OPT) && onerel && onerel->rd_rel) {BlockNumber pages = onerel->rd_rel->relpages;/* 在此处重用索引估算(基于相同类型的估算更好) */double estimated_relpages = (double)estimate_index_blocks(onerel, totalrows, table_factor);table_factor = (double)pages / estimated_relpages;/* 如果表因子太小,则使用1.0 */table_factor = table_factor > 1.0 ? table_factor : 1.0;}/** 对于索引,情况类似。清理总是扫描所有索引,因此如果我们在VACUUM的一部分,* 则不要覆盖VACUUM已插入的准确计数。*/if (!((unsigned int)vacstmt->options & VACOPT_VACUUM) || ((unsigned int)vacstmt->options & VACOPT_ANALYZE)) {for (int ind = 0; ind < nindexes; ind++) {AnlIndexData* thisdata = &indexdata[ind];double totalindexrows;BlockNumber nblocks = 0;totalindexrows = ceil(thisdata->tupleFract * totalrows);/** @global stats* 更新CN中发出分析的地方的索引的全局relpages和全局reltuples的pg_class。*/if (IS_PGXC_COORDINATOR && ((unsigned int)vacstmt->options & VACOPT_ANALYZE) &&(0 != vacstmt->pstGlobalStatEx[vacstmt->tableidx].totalRowCnts)) {nblocks = estimate_index_blocks(Irel[ind], totalindexrows, table_factor);Relation classRel = heap_open(RelationRelationId, RowExclusiveLock);vac_update_relstats(Irel[ind], classRel, nblocks, totalindexrows, 0, false, BootstrapTransactionId);heap_close(classRel, RowExclusiveLock);continue;}/* 不要为VACUUM更新pg_class。 */if ((unsigned int)vacstmt->options & VACOPT_VACUUM)break;nblocks = GetOneRelNBlocks(onerel, Irel[ind], vacstmt, totalindexrows);Relation classRel = heap_open(RelationRelationId, RowExclusiveLock);vac_update_relstats(Irel[ind], classRel, nblocks, totalindexrows, 0, false, InvalidTransactionId);heap_close(classRel, RowExclusiveLock);}}
}

这个函数的执行过程如下:

  1. 函数开始,它接受多个参数,包括一个表示要分析或清理的关系onerel)以及与该操作相关的其他信息和统计数据
  2. 首先,函数初始化一个名为 updrelpages 的变量,将其设置为与 relpages 相等,relpages 表示关系中的页面数
  3. 然后,函数检查当前会话是否在 PostgreSQL 数据节点上运行(IS_PGXC_DATANODE)。如果是在数据节点上执行,它会继续执行更新 pg_class 目录表中的统计信息的操作。这包括计算和更新与关系有关的各种统计数据,如 relpagestotalrowstotaldeadrows 等,这些统计数据对于查询优化非常重要。
  4. 如果关系是列存储表(RelationIsColStore(onerel)),则会根据特定的算法估算 relpages 的值。这个值是为了帮助数据库系统更好地了解表的物理存储结构
  5. 接着,它计算一个叫做 table_factor表扩展因子,用于查询优化。这个因子根据关系的页面数和一些估算值计算而来。
  6. 然后,函数会对关系的索引进行循环迭代,为每个索引计算统计信息,包括块数(nblocks)总索引行数。如果这是在协调器节点上执行 ANALYZE 操作,它会更新每个索引的 pg_class 中的统计信息。
  7. 最后,如果这不是一个 VACUUM 操作,函数还会更新每个索引pg_class 统计信息,并考虑之前计算的表扩展因子table_factor)。

ReceivePageAndTuple 函数

  在函数 update_pages_and_tuples_pgclass 中提到:

/*
* we have updated pg_class in function ReceivePageAndTuple when we receive pg_class from dn1,
* so, we only update pg_class on datanodes this place.
*/

  这里所表达的含义如下:

  ReceivePageAndTuple 函数和 update_pages_and_tuples_pgclass 函数之间的关系是,ReceivePageAndTuple 函数用于接收从远程节点传来的统计信息,并将这些信息用于更新 pg_class 中的页面、元组等统计数据。这是分布式数据库系统中的一种机制,用于从远程节点获取统计信息并将其同步到中央数据库。一般情况下,ReceivePageAndTuple 函数在远程节点执行,而 update_pages_and_tuples_pgclass 函数在中央数据库执行。
  具体来说,ReceivePageAndTuple 函数用于接收来自远程节点的统计信息并将其传递给中央数据库,然后中央数据库中的 update_pages_and_tuples_pgclass 函数会使用这些统计信息来更新 pg_class 中的相关数据,以维护关系的统计信息。这种机制允许分布式数据库系统在多个节点上分布式地维护更新统计信息,以支持查询优化性能调优

  ReceivePageAndTuple 函数源码如下所示:(路径:src/common/backend/pgxc_single/pool/execRemote.cpp

/** update pages, tuples, etc in pg_class* 更新pg_class中的页面、元组等信息*/
static void ReceivePageAndTuple(Oid relid, TupleTableSlot* slot, VacuumStmt* stmt)
{Relation rel;Relation classRel;RelPageType relpages;double reltuples;BlockNumber relallvisible;bool hasindex = false;// 从TupleTableSlot中获取并初始化关系的统计信息relpages = (RelPageType)DatumGetFloat8(slot->tts_values[0]);reltuples = (double)DatumGetFloat8(slot->tts_values[1]);relallvisible = (BlockNumber)DatumGetInt32(slot->tts_values[2]);hasindex = DatumGetBool(slot->tts_values[3]);// 打开关系和pg_class表rel = relation_open(relid, ShareUpdateExclusiveLock);classRel = heap_open(RelationRelationId, RowExclusiveLock);// 调用vac_update_relstats函数来更新pg_class中的统计信息vac_update_relstats(rel, classRel, relpages, reltuples, relallvisible, hasindex, BootstrapTransactionId);/* 保存标识是否有脏数据的标志到stmt中 */if (stmt != NULL) {stmt->pstGlobalStatEx[stmt->tableidx].totalRowCnts = reltuples;}/** 从远程DN/CN不获取已删除元组的信息,仅将deadtuples设置为0。* 这不会有影响,因为我们应该从所有数据节点获取已删除元组的信息来计算用户定义表的已删除元组信息。*/if (!IS_PGXC_COORDINATOR || IsConnFromCoord())pgstat_report_analyze(rel, (PgStat_Counter)reltuples, (PgStat_Counter)0);// 关闭pg_class表和关系heap_close(classRel, NoLock);relation_close(rel, NoLock);
}

estimate_cstore_blocks 函数

  函数 estimate_cstore_blocks 用于估算列存储表所需的页数,以便进行资源分配存储优化。它考虑了元组的宽度总元组数以及存储格式DFS或非DFS),并根据这些因素计算出所需的总页数。这对于数据库的存储查询性能优化非常重要。
  update_pages_and_tuples_pgclass 函数源码如下所示:(路径:src/gausskernel/optimizer/commands/analyze.cpp

static BlockNumber estimate_cstore_blocks(Relation rel, VacAttrStats** vacAttrStats, int attrCnt, double sampleTuples, double totalTuples, bool dfsStore)
{int tuple_width = 0; // 初始化元组宽度为0BlockNumber total_pages = 0; // 初始化总页数为0int i;bool isPartition = RelationIsPartition(rel); // 检查关系是否为分区表if (totalTuples <= 0) {return 0; // 如果总元组数小于等于0,返回0页}/* 计算元组宽度,循环遍历关系的属性 */for (i = 1; i <= RelationGetNumberOfAttributes(rel); i++) {Form_pg_attribute att = rel->rd_att->attrs[i - 1]; // 获取属性信息int32 item_width = -1; // 初始化属性宽度为-1if (att->attisdropped)continue; // 如果属性被删除,跳过item_width = get_typlen(att->atttypid); // 获取属性的类型宽度if (item_width > 0) {/* 对于定长数据类型,累加宽度 */tuple_width += item_width;continue;}if (sampleTuples > 0) {/* 从当前正在执行的统计信息中获取 stawidth */bool found = false;for (int attIdx = 0; attIdx < attrCnt; ++attIdx) {VacAttrStats* stats = vacAttrStats[attIdx];if (att->attnum == stats->tupattnum) {item_width = stats->stawidth;found = true;break;}}if (found) {tuple_width += item_width;continue;}}if (item_width <= 0) /* 从现有的统计信息中获取 stawidth */item_width = get_attavgwidth(RelationGetRelid(rel), i, isPartition);if (item_width <= 0) /* 获取属性类型值的平均宽度 */item_width = get_typavgwidth(att->atttypid, att->atttypmod);Assert(item_width > 0);tuple_width += item_width; // 累加属性宽度到元组宽度}if (dfsStore)total_pages = ceil((totalTuples * tuple_width * ESTIMATE_BLOCK_FACTOR) / BLCKSZ);elsetotal_pages = ceil(totalTuples / RelDefaultFullCuSize) * (RelDefaultFullCuSize * tuple_width / BLCKSZ);if (totalTuples > 0 && totalTuples < total_pages)total_pages = totalTuples; // 如果总元组数小于总页数,将总页数设为总元组数if (totalTuples > 0 && total_pages <= 0)total_pages = 1; // 如果总元组数大于0且总页数小于等于0,将总页数设为1return total_pages; // 返回估算的总页数
}

注:stawidth 表示统计信息中的一个字段,它用于存储某一属性的估算宽度width这个宽度是指在关系中存储该属性的平均字节数。在数据库统计信息中,为了更好地进行查询优化和成本估算,通常会估算每个属性的宽度,这有助于数据库系统决定如何在磁盘上存储数据以及如何执行查询计划
  具体来说,stawidth 存储了该属性的平均宽度,它是根据对表的样本数据进行统计估算得出的。这个估算值可以用于计算数据存储和传输成本,以便数据库系统可以更好地规划查询执行计划资源分配
  在数据库中,属性的宽度是指该属性的数据在磁盘上占用的字节数。不同的数据类型(例如整数文本日期等)具有不同的宽度。因此,估算每个属性的宽度对于数据库系统来说是非常重要的,它有助于优化查询性能和资源利用
  stawidth 通常在数据库的统计信息中使用,以帮助查询优化器做出更好的决策。在上面的代码示例中,estimate_cstore_blocks 函数使用 stawidth 来估算列存储表的页数,以帮助数据库管理和查询优化

get_attavgwidth 函数

  在 estimate_cstore_blocks 函数中使用 get_attavgwidth 函数来查询表的属性的平均宽度,以帮助数据库系统进行查询优化成本估算。它首先检查表是否处于升级模式,如果是,则直接返回0。然后,它根据表的持久性属性号来决定是获取全局临时表的属性统计信息还是表的属性统计信息。最后,它返回属性的平均宽度,如果没有可用的统计信息,则返回0。其函数源码如下:(路径:src/common/backend/utils/cache/lsyscache.cpp

/** get_attavgwidth* 给定表和属性号,获取该列中条目的平均宽度。如果没有可用数据,则返回零。** 当前只用于单个表,不用于继承树,因此不需要 "inh" 参数。* 在这一点上调用挂接看起来有些奇怪,但是因为优化器调用这个函数而没有其他方式让插件控制结果,所以需要挂接。*/
int32 get_attavgwidth(Oid relid, AttrNumber attnum, bool ispartition)
{HeapTuple tp;int32 stawidth;char stakind = ispartition ? STARELKIND_PARTITION : STARELKIND_CLASS;// 如果处于升级模式,返回0if (u_sess->attr.attr_common.upgrade_mode != 0)return 0;// 如果不是分区表并且表的持久性是全局临时表,则获取全局临时表的属性统计信息if (!ispartition && get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP) {tp = get_gtt_att_statistic(relid, attnum);if (!HeapTupleIsValid(tp)) {return 0;}stawidth = ((Form_pg_statistic)GETSTRUCT(tp))->stawidth;if (stawidth > 0) {return stawidth;} else {return 0;}}// 否则,获取表的属性统计信息tp = SearchSysCache4(STATRELKINDATTINH, ObjectIdGetDatum(relid), CharGetDatum(stakind), Int16GetDatum(attnum), BoolGetDatum(false));if (HeapTupleIsValid(tp)) {stawidth = ((Form_pg_statistic)GETSTRUCT(tp))->stawidth;ReleaseSysCache(tp);if (stawidth > 0)return stawidth;}// 如果没有有效的统计信息,返回0return 0;
}

get_typavgwidth 函数

  get_typavgwidth 函数用于估算指定数据类型的平均宽度,以帮助规划器进行查询计划成本估算。如果数据类型是固定宽度的(typlen > 0),则直接返回该类型的长度。如果数据类型不是固定宽度的,根据最大宽度maxwidth)和数据类型来猜测典型数据宽度。如果不知道最大宽度,函数采用一个默认的猜测值。这有助于规划器在不知道确切宽度的情况下做出估算,以便更好地选择查询执行计划。

/** get_typavgwidth* 给定类型 OID 和类型修饰(typmod)值(如果不知道 typmod,则传递 -1),* 估算该类型值的平均宽度。这用于规划器(planner),* 不需要绝对正确的结果;如果我们不确定,猜测也可以。*/
int32 get_typavgwidth(Oid typid, int32 typmod)
{int typlen = get_typlen(typid); // 获取类型的长度(字节数)int32 maxwidth;/** 如果是固定宽度类型,直接返回类型的长度*/if (typlen > 0) {return typlen;}/** type_maximum_size 知道某些数据类型的 typmod 的编码;* 不要在这里重复这个知识。*/maxwidth = type_maximum_size(typid, typmod);if (maxwidth > 0) {/** 对于 BPCHAR(定长字符类型),最大宽度也是唯一的宽度。* 否则,我们需要根据最大宽度来猜测典型数据宽度。* 对于最大宽度,假设有一个百分比的典型数据宽度是合理的。*/if (typid == BPCHAROID) {return maxwidth;}if (maxwidth <= 32) {return maxwidth; /* 假设是全宽度 */}if (maxwidth < 1000) {return 32 + (maxwidth - 32) / 2; /* 假设是 50% */}/** 超过1000后,假设我们正在处理类似 "varchar(10000)" 这样的类型,* 其限制并不经常达到,因此使用一个固定的估计值。*/return 32 + (1000 - 32) / 2;}/** 如果不知道最大宽度,采用猜测。*/return 32;
}

vac_update_relstats 函数

  函数 vac_update_relstats 用于在执行 VACUUMANALYZE 操作时,更新一个关系(表或索引)的统计信息,以确保查询优化和成本估算的准确性。函数的核心功能是通过修改 pg_class 表中的相应条目,更新关系的各种统计信息,包括页数元组数量可见页数是否有索引等。这些统计信息对于数据库系统的性能优化和查询计划非常重要。其函数源码如下所示:(路径:src/gausskernel/optimizer/commands/vacuum.cpp

/** vac_update_relstats() -- 更新一个关系的统计信息** 更新保存在 `pg_class` 表中的关系的整体统计信息,这些统计信息会被用于查询优化和成本估算。* 如果进行 ANALYZE,还会更新其他统计信息,但总是要更新这些统计信息。* 此函数适用于 `pg_class` 表中的索引和堆关系条目。** 我们违反了事务语义,通过使用新值覆盖了关系的现有 `pg_class` 元组。这是合理的,因为不管这个事务是否提交,新值都是正确的。* 这样做的原因是,如果我们按常规方式更新这些元组,那么对 `pg_class` 自身的 VACUUM 将无法很好地工作 --- 在 VACUUM 周期结束时,* `pg_class` 中的大多数元组都将变得过时。当然,这仅适用于固定大小且非 NULL 的列,而这些列确实是如此。** 注意另一个假设:不会有两个表的 VACUUM/ANALYZE 同时运行,也不会有 VACUUM/ANALYZE 与添加索引、规则或触发器等模式更改并行运行。* 否则,我们对 relhasindex 等的更新可能会覆盖未提交的更新。** 另一个采用这种方式的原因是,当我们处于延迟 VACUUM 并且设置了 PROC_IN_VACUUM 时,我们不能进行任何更新 ---* `pg_class` 中的某些元组可能会认为它们可以删除 xid = 我们的 xid 的元组。* isdirty - 用于标识关系的数据是否发生了更改。** 此函数由 VACUUM 和 ANALYZE 共享。*/
void vac_update_relstats(Relation relation, Relation classRel, RelPageType num_pages, double num_tuples,BlockNumber num_all_visible_pages, bool hasindex, TransactionId frozenxid)
{Oid relid = RelationGetRelid(relation);HeapTuple ctup;HeapTuple nctup = NULL;Form_pg_class pgcform;bool dirty = false;bool isNull = false;TransactionId relfrozenxid;Datum xid64datum;bool isGtt = false;/* 对于全局临时表,将 relstats 保存到 localhash 和 rel->rd_rel,而不是 catalog 中 */if (RELATION_IS_GLOBAL_TEMP(relation)) {isGtt = true;up_gtt_relstats(relation,static_cast<unsigned int>(num_pages), num_tuples,num_all_visible_pages,frozenxid);}/* 获取要修改的元组的副本 */ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));if (!HeapTupleIsValid(ctup))ereport(ERROR,(errcode(ERRCODE_NO_DATA_FOUND), errmsg("在进行 VACUUM 期间,relid %u 的 pg_class 条目已经消失", relid)));pgcform = (Form_pg_class)GETSTRUCT(ctup);/* 对复制的元组应用必需的更新,如果有的话 */dirty = false;
#ifdef PGXC// frozenxid == BootstrapTransactionId 表示是由 execRemote.cpp:ReceivePageAndTuple() 调用if (IS_PGXC_DATANODE || (frozenxid == BootstrapTransactionId) || IsSystemRelation(relation)) {
#endifif (isGtt) {relation->rd_rel->relpages = (int32) num_pages;relation->rd_rel->reltuples = (float4) num_tuples;relation->rd_rel->relallvisible = (int32) num_all_visible_pages;} else {if (pgcform->relpages - num_pages != 0) {pgcform->relpages = num_pages;dirty = true;}if (pgcform->reltuples - num_tuples != 0) {pgcform->reltuples = num_tuples;dirty = true;}if (pgcform->relallvisible != (int32)num_all_visible_pages) {pgcform->relallvisible = (int32)num_all_visible_pages;dirty = true;}}
#ifdef PGXC}
#endifif (pgcform->relhasindex != hasindex) {pgcform->relhasindex = hasindex;dirty = true;}/** 如果我们发现没有索引,那么也没有主键。这可能需要更加彻底的处理...*/if (pgcform->relhaspkey && !hasindex) {pgcform->relhaspkey = false;dirty = true;}/* 如果需要,我们还会清除 relhasrules 和 relhastriggers */if (pgcform->relhasrules && relation->rd_rules == NULL) {pgcform->relhasrules = false;dirty = true;}if (pgcform->relhastriggers && relation->trigdesc == NULL) {pgcform->relhastriggers = false;dirty = true;}/** relfrozenxid 不应该回退,除非在 PGXC 中,当 xid 与 gxid 不同步且我们希望使用独立后台进程来纠正时。* 调用方可以传递 InvalidTransactionId,如果没有新数据。*/xid64datum = tableam_tops_tuple_getattr(ctup, Anum_pg_class_relfrozenxid64, RelationGetDescr(classRel), &isNull);if (isNull) {relfrozenxid = pgcform->relfrozenxid;if (TransactionIdPrecedes(t_thrd.xact_cxt.ShmemVariableCache->nextXid, relfrozenxid) ||!TransactionIdIsNormal(relfrozenxid))relfrozenxid = FirstNormalTransactionId;} else {relfrozenxid = DatumGetTransactionId(xid64datum);}if (TransactionIdIsNormal(frozenxid) && (TransactionIdPrecedes(relfrozenxid, frozenxid)
#ifdef PGXC|| !IsPostmasterEnvironment)
#endif) {Datum values[Natts_pg_class];bool nulls[Natts_pg_class];bool replaces[Natts_pg_class];errno_t rc;pgcform->relfrozenxid = (ShortTransactionId)InvalidTransactionId;rc = memset_s(values, sizeof(values), 0, sizeof(values));securec_check(rc, "", "");rc = memset_s(nulls, sizeof(nulls), false, sizeof(nulls));securec_check(rc, "", "");rc = memset_s(replaces, sizeof(replaces), false, sizeof(replaces));securec_check(rc, "", "");replaces[Anum_pg_class_relfrozenxid64 - 1] = true;values[Anum_pg_class_relfrozenxid64 - 1] = TransactionIdGetDatum(frozenxid);nctup = (HeapTuple) tableam_tops_modify_tuple(ctup, RelationGetDescr(classRel), values, nulls, replaces);ctup = nctup;dirty = true;}/* 如果有任何更改,将元组写入 */if (dirty) {if (isNull && nctup) {simple_heap_update(classRel, &ctup->t_self, ctup);CatalogUpdateIndexes(classRel, ctup);} elseheap_inplace_update(classRel, ctup);if (nctup)heap_freetuple(nctup);}
}

测试案例

1. 创建列存储表,执行以下 SQL

postgres=# create table t2 (id int) with (orientation=column);
CREATE TABLE
postgres=# insert into t2 values (generate_series(1,10));
INSERT 0 10
postgres=# analyze t2;

2. 步入 estimate_cstore_blocks 函数

调试信息如下:

  1. 首先检查是否为分区表,gdb调试信息如下:
bool isPartition = RelationIsPartition(rel); 
---------------------------------------------
(gdb) p isPartition
$1 = false
  1. 检查属性是否被删除,gdb调试信息如下:
if (att->attisdropped)continue;
---------------------------------------------
(gdb) p isPartition
(gdb) p att->attisdropped
$2 = false
  1. 获取属性类型宽度:
item_width = get_typlen(att->atttypid);
---------------------------------------------
(gdb) p item_width
$3 = 4
  1. 根据判断是否为 DFS 表来计算总页数 total_pages
if (dfsStore)total_pages = ceil((totalTuples * tuple_width * ESTIMATE_BLOCK_FACTOR) / BLCKSZ);
elsetotal_pages = ceil(totalTuples / RelDefaultFullCuSize) * (RelDefaultFullCuSize * tuple_width / BLCKSZ);
---------------------------------------------
(gdb) p total_pages
$4 = 29
  1. 根据判断 totalTuplestotal_pages 的大小来为总页数赋值:
if (totalTuples > 0 && totalTuples < total_pages)total_pages = totalTuples;if (totalTuples > 0 && total_pages <= 0)total_pages = 1;
---------------------------------------------
(gdb) p total_pages
$4 = 10

以上两段代码含义如下:

  1. 如果 totalTuples(实际数据记录数)小于 total_pages(计算出的页数),这可能会导致问题,因为没有足够的数据来填充所有这些页,这会浪费存储空间。这样做可以确保不会浪费额外的页用于存储数据,因为页的数量应该与数据的数量相匹配。
  2. 是为了处理可能的特殊情况,当总元组数大于0但计算出的总页数小于等于0时将总页数设置为1。这可以确保总页数至少为1,以便能够存储数据。当总元组数非零但由于某些原因总页数计算为非正数时,将其设置为1是一个常见的做法,以确保有一个最小的页来存储数据

以上有关列存更新 pages 的数据已经得到了。

这篇关于【 OpenGauss源码学习 —— 列存储(update_pages_and_tuples_pgclass)】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/280792

相关文章

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo

HDFS—存储优化(纠删码)

纠删码原理 HDFS 默认情况下,一个文件有3个副本,这样提高了数据的可靠性,但也带来了2倍的冗余开销。 Hadoop3.x 引入了纠删码,采用计算的方式,可以节省约50%左右的存储空间。 此种方式节约了空间,但是会增加 cpu 的计算。 纠删码策略是给具体一个路径设置。所有往此路径下存储的文件,都会执行此策略。 默认只开启对 RS-6-3-1024k

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学