【OpenGauss源码学习 —— (ALTER TABLE(CStoreRewriter))】

ALTER TABLE(CStoreRewriter)

  • 概述
  • CStoreRewriter 类
    • 详细功能解析
    • 添加列
      • AddColumns 函数
      • AddColumnInitPhrase1 函数
      • AddColumnInitPhrase2 函数
      • AddColumnDestroy 函数
    • 设置数据类型
      • SetDataType 函数
      • SetDataTypeInitPhase1 函数
      • SetDataTypeInitPhase2 函数
      • SetDataTypeDestroy 函数
    • BeginRewriteCols 函数
    • RewriteColsData 函数
    • EndRewriteCols 函数
      • AddColumnDestroy 函数
      • SetDataTypeDestroy 函数
    • 处理 Cu 相关函数
      • HandleCuWithSameValue 函数
      • FormCuDataForTheSameVal 函数
      • CompressCuData 函数
      • SetDataTypeHandleFullNullCu 函数
      • SetDataTypeHandleSameValCu函数
      • SetDataTypeHandleNormalCu 函数
      • FetchCudescFrozenXid 函数
      • InsertNewCudescTup 函数
      • HandleWholeDeletedCu 函数

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

概述

  OpenGauss源码学习 —— (ALTER TABLE(ExecRewriteCStoreTable))】这篇文章详细介绍了 OpenGauss 数据库中 ALTER TABLE 命令的实现细节,特别是涉及到列存储表的重写操作ExecRewriteCStoreTable)。其中,CStoreRewriter 在此过程中起着核心作用,主要负责以下方面:

  1. 数据组织优化: 通过重写操作,CStoreRewriter 帮助优化存储在列存储表中的数据的物理布局,这包括数据的压缩和重组,以提高查询效率和减少存储空间。
  2. 维护数据完整性: 在重写过程中,CStoreRewriter 确保所有的表约束(如NOT NULL约束)和表结构更改(如列的添加或删除)都得到妥善处理,确保数据完整性不受影响。
  3. 更新元数据: 重写操作还涉及更新与表相关的元数据,以反映重写后的新状态,例如更新索引、表大小等信息。

  本文将详细介绍一下列存重写类型 CStoreRewriter

CStoreRewriter 类

  CStoreRewriter 类是专门设计用于 OpenGauss 数据库系统中,处理列存储表(CStore)的 ALTER TABLE 操作该类的核心目的是在表结构变动时,确保数据的有效重写、优化存储和提高查询效率。本类实现了多种复杂的数据处理功能,如数据重写列添加数据类型转换表空间更改压缩数据等。
  在 ALTER TABLE 操作中,表结构的变化可能涉及添加新列修改列类型移动数据到新的表空间等。这些操作不仅需要修改表的元数据,还需要重新组织表中的数据,以适应新的表结构。CStoreRewriter 类通过其丰富的方法集合来完成这些任务,其中包括数据的读取、重写、压缩和存储等步骤。
  该类的详细代码描述如下所示:(路径:src\include\access\cstore_rewrite.h

// 定义一个CStoreRewriter类,继承自BaseObject基类
class CStoreRewriter : public BaseObject {
public:// 构造函数,初始化旧表关系和描述符,以及新的描述符CStoreRewriter(Relation oldHeapRel, TupleDesc oldTupDesc, TupleDesc newTupDesc);// 虚拟析构函数virtual ~CStoreRewriter(){};// 用于销毁重写器的实例virtual void Destroy();// 改变表空间的方法void ChangeTableSpace(Relation CUReplicationRel);// 开始重写列的方法void BeginRewriteCols(_in_ int nRewriteCols, _in_ CStoreRewriteColumn **pRewriteCols,_in_ const int *rewriteColsNum, _in_ bool *rewriteFlags);// 重写列数据的方法void RewriteColsData();// 结束重写列的方法void EndRewriteCols();public:// 处理值相同的列存储单元(CU)template <bool append>void HandleCuWithSameValue(_in_ Relation rel, _in_ Form_pg_attribute newColAttr, _in_ Datum newColVal,_in_ bool newColValIsNull, _in_ FuncSetMinMax func, _in_ CUStorage *colStorage,__inout CUDesc *newColCudesc, __inout CUPointer *pOffset);// 为值相同的CU形成数据static void FormCuDataForTheSameVal(__inout CU *cuPtr, _in_ int rowsCntInCu, _in_ Datum newColVal,_in_ Form_pg_attribute newColAttr);// 压缩列存储单元数据的方法static void CompressCuData(CU *cuPtr, CUDesc *cuDesc, Form_pg_attribute newColAttr, int16 compressing_modes);// 保存列存储单元数据的方法FORCE_INLINEstatic void SaveCuData(_in_ CU *cuPtr, _in_ CUDesc *cuDesc, _in_ CUStorage *cuStorage);private:// 判断是否需要重写的内联方法FORCE_INLINE bool NeedRewrite() const;// 添加列的方法void AddColumns(_in_ uint32 cuId, _in_ int rowsCntInCu, _in_ bool wholeCuIsDeleted);// 初始化添加列的第一阶段void AddColumnInitPhrase1();// 初始化添加列的第二阶段void AddColumnInitPhrase2(_in_ CStoreRewriteColumn *addColInfo, _in_ int idx);// 销毁添加列的方法void AddColumnDestroy();// 设置数据类型的方法void SetDataType(_in_ uint32 cuId, _in_ HeapTuple *cudescTup, _in_ TupleDesc oldCudescTupDesc,_in_ const char *delMaskDataPtr, _in_ int rowsCntInCu, _in_ bool wholeCuIsDeleted);// 初始化设置数据类型的第一阶段void SetDataTypeInitPhase1();// 初始化设置数据类型的第二阶段void SetDataTypeInitPhase2(_in_ CStoreRewriteColumn *sdtColInfo, _in_ int idx);// 销毁设置数据类型的方法void SetDataTypeDestroy();// 处理全为NULL值的CUFORCE_INLINEvoid SetDataTypeHandleFullNullCu(_in_ CUDesc *oldColCudesc, _out_ CUDesc *newColCudesc);// 处理值相同的CUvoid SetDataTypeHandleSameValCu(_in_ int sdtIndex, _in_ CUDesc *oldColCudesc, _out_ CUDesc *newColCudesc);// 处理正常情况下的CUvoid SetDataTypeHandleNormalCu(_in_ int sdtIndex, _in_ const char *delMaskDataPtr, _in_ CUDesc *oldColCudesc,_out_ CUDesc *newColCudesc);// 获取冻结的事务IDvoid FetchCudescFrozenXid(Relation oldCudescHeap);// 插入新的列描述符元组void InsertNewCudescTup(_in_ CUDesc *pCudesc, _in_ TupleDesc pCudescTupDesc, _in_ Form_pg_attribute pColNewAttr);// 处理完全删除的CUvoid HandleWholeDeletedCu(_in_ uint32 cuId, _in_ int rowsCntInCu, _in_ int nRewriteCols,_in_ CStoreRewriteColumn **rewriteColsInfo);// 刷新所有CU数据void FlushAllCUData() const;private:// 旧表关系Relation m_OldHeapRel;// 旧元组描述符TupleDesc m_OldTupDesc;// 新元组描述符TupleDesc m_NewTupDesc;// 新的列描述符堆的对象标识符Oid m_NewCuDescHeap;// 列重写标志数组bool *m_ColsRewriteFlag;// 设置新的表空间标志bool m_TblspcChanged;// 目标表空间的对象标识符Oid m_TargetTblspc;// 目标关系文件节点的对象标识符Oid m_TargetRelFileNode;// 用于CU复制的新关系,如果表空间已更改,则使用特殊的CU复制关系Relation m_CUReplicationRel;// 设置数据类型列追加偏移量的指针数组CUPointer *m_SDTColAppendOffset;// 添加列的数量int m_AddColsNum;// 添加列信息的指针数组CStoreRewriteColumn **m_AddColsInfo;// 添加列存储的指针数组CUStorage **m_AddColsStorage;// 设置最小最大函数的指针数组FuncSetMinMax *m_AddColsMinMaxFunc;// 添加列追加偏移量的指针数组CUPointer *m_AddColsAppendOffset;// 设置数据类型的列数量int m_SDTColsNum;// 设置数据类型列信息的指针数组CStoreRewriteColumn **m_SDTColsInfo;// 读取设置数据类型列的存储指针数组CUStorage **m_SDTColsReader;// 设置数据类型列最小最大函数的指针数组FuncSetMinMax *m_SDTColsMinMaxFunc;// 写入设置数据类型列的存储指针数组CUStorage **m_SDTColsWriter;// 设置数据类型列值的数据数组Datum *m_SDTColValues;// 设置数据类型列是否为空的布尔数组bool *m_SDTColIsNull;// 新的列描述符关系Relation m_NewCudescRel;// 新的列描述符批量插入处理HeapBulkInsert *m_NewCudescBulkInsert;// 冻结的事务IDTransactionId m_NewCudescFrozenXid;// 在重写表数据期间使用的EStateEState *m_estate;// 表达式上下文,每处理完一条数据后必须重置其每条数据内存ExprContext *m_econtext;
};

详细功能解析

  1. 重写列数据:
      BeginRewriteColsRewriteColsDataEndRewriteCols 方法构成了数据重写的主要流程。在开始重写列之前,先通过 BeginRewriteCols 方法设置重写的列信息和相关参数。随后,RewriteColsData 方法实际进行数据的读取、转换和重新组织。最后,EndRewriteCols 方法结束重写过程,并进行必要的清理工作。
  2. 数据压缩与存储:
      通过 CompressCuDataSaveCuData 方法,CStoreRewriter 对列存储单元(CU)中的数据进行压缩处理,以减少数据占用的存储空间,并提高数据的读取效率。这些方法确保数据在物理存储上的优化,是数据库性能提升的关键。
  3. 类型转换与列添加:
      类中的 AddColumnsSetDataType 等方法处理数据类型的转换和新列的添加。这些功能对于数据库表结构的灵活性和扩展性至关重要,特别是在动态调整数据库模式以适应应用需求变更时。
  4. 表空间管理:
      ChangeTableSpace 方法允许将数据从一个表空间迁移到另一个表空间。这在进行数据库维护或优化时非常有用,比如当某个表空间的磁盘空间不足,或者需要将数据迁移到性能更高的存储设备上时。
  5. 事务与元数据管理:
      类中还包含了诸如 FetchCudescFrozenXidInsertNewCudescTup 等方法,这些方法负责处理与数据重写相关的事务管理和元数据更新。例如,更新列描述信息,确保数据一致性和完整性。

添加列

  AddColumns、AddColumnInitPhrase1、AddColumnInitPhrase2、AddColumnDestroy 这四个函数是 CStoreRewriter 类中用于处理列存储表中添加新列的一系列方法,它们协同工作以确保在列存储单元CU)中正确地添加新列

  1. AddColumns: 此函数负责在特定的列存储单元中添加新列。它接受列存储单元的标识符cuId)、该单元中的行数rowsCntInCu)以及一个标志(wholeCuIsDeleted),指示该单元是否已完全删除。这是添加新列的初步操作,涉及到更新数据结构以便包含新列。
  2. AddColumnInitPhrase1: 这个函数启动添加新列的初始化过程,设置必要的数据结构和状态,为新列的数据插入做准备。这是添加列操作的准备阶段,确保后续操作可以顺利进行。
  3. AddColumnInitPhrase2: 紧接着第一阶段,这个函数继续初始化过程,具体处理每个新添加的列的信息。它通过接收一个指向 CStoreRewriteColumn 的指针(表示新列的信息)和列的索引,来精确地配置每列的特定参数和状态。
  4. AddColumnDestroy: 在新列成功添加后,这个函数负责清理与添加新列相关的所有临时数据结构和状态,确保系统资源得到适当释放,并维护系统的稳定性和性能。

AddColumns 函数

  AddColumns 函数主要实现在列存储表中添加新列的功能。当 ALTER TABLE 命令需要向表中添加列时,此函数会被调用。它首先检查目标列存储单元(CU)是否完全被删除,如果是,则只需要处理元数据而不需要写入任何数据。接下来,代码通过创建一个假的元组和元组槽来模拟一个环境,用于计算可能需要通过表达式定义的新列值。之后,它遍历所有要添加的列,为每个列计算值(如果有表达式的话),更新列存储描述符CUDesc),然后调用 HandleCuWithSameValue 来处理值相同的 CU。最后,新的列描述符被插入到相应的表中,完成列的添加过程。函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.cpp

void CStoreRewriter::AddColumns(_in_ uint32 cuId, _in_ int rowsCntInCu, _in_ bool wholeCuIsDeleted)
{// 确认cuId有效,cuId必须大于第一个CU的IDAssert(cuId > (uint32)FirstCUID);/* description: future plan-remove this branch after WHOLE DELETED CU data can be reused. */// 如果整个CU已被删除,则不需要写入任何CU数据if (wholeCuIsDeleted) {// 但是每个列的描述符(cudesc)元组必须形成并插入HandleWholeDeletedCu(cuId, rowsCntInCu, m_AddColsNum, m_AddColsInfo);return;}// 构造一个假的元组来欺骗函数 ExecEvalExpr(),表示添加的新列值不依赖于任何现有列或其值HeapTuple fakeTuple = CreateFakeHeapTup(m_NewTupDesc, -1, 0);TupleTableSlot* fakeSlot = MakeSingleTupleTableSlot(m_NewTupDesc);(void)ExecStoreTuple(fakeTuple, fakeSlot, InvalidBuffer, false);// 遍历所有需要添加的列for (int i = 0; i < m_AddColsNum; ++i) {CStoreRewriteColumn* newColInfo = m_AddColsInfo[i];// 确保新列信息有效,且是被添加的,而不是被删除的Assert(newColInfo && newColInfo->isAdded && !newColInfo->isDropped);int attrIndex = newColInfo->attrno - 1;Form_pg_attribute newColAttr = &m_NewTupDesc->attrs[attrIndex];CUDesc newColCudesc;newColCudesc.cu_id = cuId;newColCudesc.row_count = rowsCntInCu;Datum newColVal = (Datum)0;bool newColValIsNull = !newColInfo->notNull;// 如果新列的值是通过表达式计算的,则计算新值if (newColInfo->newValue) {m_econtext->ecxt_scantuple = fakeSlot;newColVal = ExecEvalExpr(newColInfo->newValue->exprstate, m_econtext, &newColValIsNull);} else if (newColInfo->notNull) {// 如果新列为NOT NULL但没有默认值,则抛出错误ereport(ERROR,(errcode(ERRCODE_NOT_NULL_VIOLATION),errmsg("column \"%s\" contains null values", NameStr(newColAttr->attname)),errdetail("existing data violate the NOT NULL constraint of new column."),errhint("define DEFAULT constraint also.")));}newColCudesc.magic = GetCurrentTransactionIdIfAny();// 处理值相同的列存储单元HandleCuWithSameValue<true>(m_OldHeapRel,newColAttr,newColVal,newColValIsNull,m_AddColsMinMaxFunc[i],m_AddColsStorage[i],&newColCudesc,m_AddColsAppendOffset + i);// 插入新的列描述符元组InsertNewCudescTup(&newColCudesc, RelationGetDescr(m_NewCudescRel), newColAttr);// 重置表达式上下文ResetExprContext(m_econtext);}// 释放假元组heap_freetuple(fakeTuple);fakeTuple = NULL;// 释放假元组槽ExecDropSingleTupleTableSlot(fakeSlot);fakeSlot = NULL;
}

  注: 在数据库操作中,尤其是在处理 ALTER TABLE 添加新列的情况下,创建一个假元组fake tuple)通常是为了提供一个必要的上下文环境,以便能够评估和计算新列的默认值或者表达式值。这个假元组不含有实际数据,但它模拟了数据结构,使得函数和表达式能在适当的上下文中执行,而不需要实际的数据行。下面通过一个具体的案例来说明这一点:
  假设我们有一个数据库表 employees,包含以下列:

列名含义
id员工ID
name员工姓名

  现在我们需要通过 ALTER TABLE 命令给这个表添加一个新列 entry_date(入职日期),这个新列的默认值应该是执行添加操作当天的日期。这个日期可以通过 内置函数 CURRENT_DATE 获得。为了在不实际插入或更新任何现存数据行的情况下评估 CURRENT_DATE,我们需要创建一个假元组。这个假元组将被用作执行 CURRENT_DATE 函数的上下文环境,具体代码示例如下所示:

// 假设这是ALTER TABLE添加列的函数实现部分
void AddEntryDateColumn() {// 创建一个新的元组描述符,假设包含了新列`entry_date`TupleDesc newTupDesc = CreateTupleDescWithEntryDate();// 创建一个假元组,但这里我们不需要实际的数据值HeapTuple fakeTuple = CreateFakeHeapTup(newTupDesc, -1, 0);// 创建一个单元组槽,用于存放假元组TupleTableSlot* fakeSlot = MakeSingleTupleTableSlot(newTupDesc);// 将假元组存储到槽中ExecStoreTuple(fakeTuple, fakeSlot, InvalidBuffer, false);// 设定上下文中的扫描元组为我们的假元组槽ExpressionContext* econtext = CreateStandaloneExprContext();econtext->ecxt_scantuple = fakeSlot;// 评估当前日期函数,获取默认值Datum newColVal = ExecEvalExpr(CURRENT_DATE(), econtext, &newColValIsNull);// 此处处理与新列相关的其他逻辑,例如更新列描述符等// 清理资源heap_freetuple(fakeTuple);ExecDropSingleTupleTableSlot(fakeSlot);FreeExprContext(econtext, true);
}// 注意:上述代码是一个高层次的示意,实际实现会依赖具体的数据库系统API和内部结构。

  通过这种方式,我们能够在不影响或依赖任何现有数据的情况下,为新列生成一个有效且正确的默认值。这是在数据库内部处理列添加和默认值设置的一个有效策略,特别是在大规模数据环境下,直接操作实际数据行可能导致性能问题或不必要的数据加载。使用假元组可以在逻辑上简化操作,确保新列的值能够正确评估并应用,同时避免对现有数据造成干扰。

  为什么使用假元组?

  • 上下文提供: SQL 表达式需要在特定的数据上下文中被评估。对于依赖于当前数据库状态的表达式,如 CURRENT_DATE 或其他系统函数,它们需要一个元组上下文来执行。而在实际的表中可能还没有数据,或者我们不希望触及实际数据。
  • 避免实际数据修改: 使用假元组可以在不实际修改或加载任何真实数据的情况下模拟这一处理过程。这样做可以防止在大量数据的生产环境中因修改表结构而导致的性能问题。

AddColumnInitPhrase1 函数

  AddColumnInitPhrase1 函数的作用是为处理新添加的列进行初步的内存分配和数据结构准备。当 ALTER TABLE 命令需要向表中添加新列时,此函数会被调用以确保所有相关的数据结构都已经准备就绪,用于后续的数据处理和存储操作。函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.cpp

  具体而言,此函数为每一个即将添加的列分配四类资源:

  1. 列信息: 为存储新列的结构信息(如数据类型是否有默认值等)的指针数组分配内存。
  2. 列存储: 为每个新列的物理存储(如列存储单元 CU 的指针)分配内存,这些存储将用来保存实际的数据。
  3. 最小最大函数: 为处理新列数据的最小和最大值的函数分配内存,这对于优化查询和数据维护非常重要。
  4. 追加偏移量: 为记录新数据应追加到哪里的偏移量分配内存,这是处理数据追加操作的关键。
// 确保需要添加的列数大于0,否则断言失败,因为没有列需要添加时调用这个函数没有意义
Assert(m_AddColsNum > 0);// 为新添加的列信息分配内存,m_AddColsInfo 是一个指向 CStoreRewriteColumn 指针数组的指针
m_AddColsInfo = (CStoreRewriteColumn**)palloc(sizeof(CStoreRewriteColumn*) * m_AddColsNum);// 为存储新添加列的存储对象分配内存,m_AddColsStorage 是一个指向 CUStorage 指针数组的指针
m_AddColsStorage = (CUStorage**)palloc(sizeof(CUStorage*) * m_AddColsNum);// 为最小最大函数指针数组分配内存,这些函数用于处理新列的数据范围,m_AddColsMinMaxFunc 是指向 FuncSetMinMax 的指针数组
m_AddColsMinMaxFunc = (FuncSetMinMax*)palloc(sizeof(FuncSetMinMax) * m_AddColsNum);// 为新添加列的追加偏移量分配内存,这些偏移量指示数据应该被追加到列存储单元的哪个位置,m_AddColsAppendOffset 是指向 CUPointer 的指针数组
m_AddColsAppendOffset = (CUPointer*)palloc(sizeof(CUPointer) * m_AddColsNum);

AddColumnInitPhrase2 函数

  AddColumnInitPhrase2 函数为新添加的列进行详细的初始化设置,这包括为列存储配置存储空间设置列的最小/最大值函数,以及处理与表空间和文件节点相关的逻辑。函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.cpp

  1. 列信息和存储配置: 函数开始时,首先确认索引有效,然后将传入的列信息存储在对应的数组索引位置。为每个列配置最小/最大值函数,这对于后续数据的快速检索和验证很重要。
  2. 列存储的物理位置: 创建并初始化与新列相关的文件节点对象,包括设置新列可能的表空间文件节点。这对于数据的物理存储至关重要,尤其是当表空间发生变化时,需要正确地反映这些变化。
  3. 日志记录和错误处理: 如果表空间改变,函数会记录相关日志,帮助追踪操作的历史和定位可能的问题。
  4. 存储创建和锁管理: 为记录新数据应追加到哪里的偏移量分配内存,这是处理数据追加操作的关键。
  5. 追加偏移量的初始化: 最后,设置追加偏移量为0,准备开始数据追加操作,这是为后续的数据写入做准备。
void CStoreRewriter::AddColumnInitPhrase2(_in_ CStoreRewriteColumn* addColInfo, _in_ int idx)
{// 确保要添加的列数大于0,以及索引 idx 在有效范围内Assert(m_AddColsNum > 0);Assert(idx >= 0 && idx < m_AddColsNum);// 将传入的列信息对象存储到指定索引位置m_AddColsInfo[idx] = addColInfo;/* 为粗略检查设置最小/最大值函数 */// 根据新列的数据类型,获取并设置相应的最小/最大值函数m_AddColsMinMaxFunc[idx] = GetMinMaxFunc(m_NewTupDesc->attrs[addColInfo->attrno - 1].atttypid);/* 可能会使用不同的表空间和文件节点 */// 创建一个新的文件节点对象,用于列存储RelFileNode relfilenode = m_OldHeapRel->rd_node;relfilenode.spcNode = m_TargetTblspc; // 设置目标表空间relfilenode.relNode = m_TargetRelFileNode; // 设置目标文件节点CFileNode cFileNode(relfilenode, (int)addColInfo->attrno, MAIN_FORKNUM); // 为新列创建文件节点对象m_AddColsStorage[idx] = New(CurrentMemoryContext) CUStorage(cFileNode); // 创建并分配列存储// 如果表空间发生变化,记录日志if (m_TblspcChanged) {ereport(LOG,(errmsg("Column [ADD COLUMN]: %s(%u C%d) tblspc %u/%u/%u => %u/%u/%u",RelationGetRelationName(m_OldHeapRel),RelationGetRelid(m_OldHeapRel),(addColInfo->attrno - 1),m_OldHeapRel->rd_node.spcNode,m_OldHeapRel->rd_node.dbNode,m_OldHeapRel->rd_node.relNode,m_TargetTblspc,m_OldHeapRel->rd_node.dbNode,m_TargetRelFileNode)));}/* 处理表空间未变更的情况 */// 锁定文件节点重用锁,共享模式LWLockAcquire(RelfilenodeReuseLock, LW_SHARED);// 创建存储,参数指示是否重用现有文件m_AddColsStorage[idx]->CreateStorage(0, !m_TblspcChanged);// 创建关联的列存储CStoreRelCreateStorage(&cFileNode.m_rnode, addColInfo->attrno, m_OldHeapRel->rd_rel->relpersistence, m_OldHeapRel->rd_rel->relowner);// 释放锁LWLockRelease(RelfilenodeReuseLock);/* 为新的列存储单元数据使用仅追加方法 */// 设置追加偏移量为0,表示从头开始追加数据m_AddColsAppendOffset[idx] = 0;
}

AddColumnDestroy 函数

  AddColumnDestroy 函数的作用是在数据库的 ALTER TABLE ADD COLUMN 操作完成后,进行清理和资源释放,以确保不会有内存泄露其他资源占用问题。当添加新列的操作涉及到为新列分配多种资源(如存储空间控制结构等)时,这些资源在操作完成后需要被适当地回收。函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.cpp

// 释放添加列操作后所有未使用的资源。
void CStoreRewriter::AddColumnDestroy()
{// 检查是否有需要清理的列资源if (m_AddColsNum > 0) {// 循环通过所有添加的列for (int i = 0; i < m_AddColsNum; ++i) {// 使用 DELETE_EX 宏删除每个列的存储对象,安全释放内存DELETE_EX(m_AddColsStorage[i]);}// 释放存储新列信息的数组pfree_ext(m_AddColsInfo);// 释放存储新列存储对象指针的数组pfree_ext(m_AddColsStorage);// 释放存储最小/最大函数指针的数组pfree_ext(m_AddColsMinMaxFunc);// 释放存储新列追加偏移量的数组pfree_ext(m_AddColsAppendOffset);}
}

设置数据类型

  SetDataType、SetDataTypeInitPhase1、SetDataTypeInitPhase2SetDataTypeDestroy 四个函数用于管理和处理列存储表中数据类型的变更过程SetDataType 函数负责具体的数据类型设置工作,它根据提供的参数(如列存储单元 ID描述元组旧描述符删除掩码行数和删除状态)更新列存储单元的数据类型。SetDataTypeInitPhase1SetDataTypeInitPhase2 函数分别代表初始化数据类型设置过程的两个阶段,第一阶段可能涉及准备必要的数据结构和临时资源,而第二阶段则具体处理每个列的数据类型转换和相关属性设置。最后,SetDataTypeDestroy 函数用于清理在数据类型设置过程中可能产生的任何临时资源或分配,确保资源的有效管理和释放,维护系统的稳定性和效率。这一系列函数确保在表结构变化时数据类型的正确转换和更新,是数据库表结构管理的重要组成部分。

SetDataType 函数

  SetDataType 函数是处理 ALTER TABLE ALTER COLUMN SET DATA TYPE 操作的核心方法,它负责更新指定列存储单元中列的数据类型。此函数首先检查是否整个列存储单元已被删除,若是则只处理列描述符而不处理数据。若未被删除,则遍历每一个需要更新数据类型的列,对每个列的描述符进行解构和重构,更新旧的列描述符信息以匹配新的数据类型,并处理不同的数据状态(如全为空、值相同或正常情况)。最后,更新的列描述符会被插入新的关系描述中,同时旧的数据缓存被清理,确保数据的一致性和系统的稳定性。通过这个过程,列的数据类型得以安全且有效地更新,以适应表结构的变化。函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.cpp

// 主要功能实现部分,处理 ALTER COLUMN SET DATA TYPE 语句。
void CStoreRewriter::SetDataType(_in_ uint32 cuId, _in_ HeapTuple* cudescTup, _in_ TupleDesc cudescTupDesc,_in_ const char* delMaskDataPtr, _in_ int rowsCntInCu, _in_ bool wholeCuIsDeleted)
{// 如果整个列存储单元已被删除,则不需要处理现有的数据,但需要重新形成并插入每列的列描述符元组。if (wholeCuIsDeleted) {HandleWholeDeletedCu(cuId, rowsCntInCu, m_SDTColsNum, m_SDTColsInfo);return;}// 获取列存储缓存管理器DataCacheMgr* cucache = CUCache;// 遍历所有设置数据类型的列for (int sdtIndex = 0; sdtIndex < m_SDTColsNum; ++sdtIndex) {CStoreRewriteColumn* setDataTypeColInfo = m_SDTColsInfo[sdtIndex];// 断言确保列信息有效,且列既不是新添加的也不是已删除的Assert(setDataTypeColInfo);Assert(!setDataTypeColInfo->isAdded);Assert(!setDataTypeColInfo->isDropped);// 获取属性索引int attrIndex = setDataTypeColInfo->attrno - 1;// 解构列描述元组,并设置旧的列描述对象CUDesc oldColCudesc;CStore::DeformCudescTuple(cudescTup[attrIndex], cudescTupDesc, &m_OldTupDesc->attrs[attrIndex], &oldColCudesc);// 断言确认列ID和行数正确Assert(oldColCudesc.cu_id == cuId);Assert(oldColCudesc.row_count == rowsCntInCu);// 创建新的列描述对象CUDesc newColCudesc;// 根据列的不同状态调用不同的处理函数if (oldColCudesc.IsNullCU()) {SetDataTypeHandleFullNullCu(&oldColCudesc, &newColCudesc);} else if (oldColCudesc.IsSameValCU()) {SetDataTypeHandleSameValCu(sdtIndex, &oldColCudesc, &newColCudesc);} else {SetDataTypeHandleNormalCu(sdtIndex, delMaskDataPtr, &oldColCudesc, &newColCudesc);}// 插入新的列描述元组InsertNewCudescTup(&newColCudesc, RelationGetDescr(m_NewCudescRel), &m_NewTupDesc->attrs[attrIndex]);// 重置表达式上下文ResetExprContext(m_econtext);// 使列的缓存数据无效,因为此数据将不再被使用cucache->InvalidateCU((RelFileNodeOld *)&(m_OldHeapRel->rd_node), setDataTypeColInfo->attrno - 1,oldColCudesc.cu_id, oldColCudesc.cu_pointer);}// 清除缓存管理器的引用,因为 econtext 由 estate 管理cucache = NULL;
}

  其中,m_SDTColsNum 变量通常存储需要进行数据类型更改的列的数量。它的值与 CStoreRewriteType 相关联,特别是当 CStoreRewriteTypeCSRT_SET_DATA_TYPE 时,m_SDTColsNum 用于计数此类操作。
  CStoreRewriteType 枚举定义了不同类型的列存储重写操作。这个枚举有三个值:

typedef enum CStoreRewriteType {CSRT_ADD_COL = 0,    // --> ADD COLUMN 表示添加新列的操作。CSRT_SET_DATA_TYPE,  // --> ALTER COLUMN SET DATA TYPE 表示更改列的数据类型的操作。CSRT_NUM             // add new type above please. 枚举的结束标志,也用于计数枚举值的总数。
} CStoreRewriteType;

  此外,函数 ATCStoreGetRewriteAttrs (在【OpenGauss源码学习 —— (ALTER TABLE(ExecRewriteCStoreTable))】一文中有描述)解析 ALTER TABLE 命令,确定需要执行哪些列存储的重写操作,并据此初始化相关数据结构。函数参数中的 nColsOfEachType 数组存储每种重写类型的列数量,其中索引 CSRT_ADD_COLCSRT_SET_DATA_TYPE 分别计数添加列和更改数据类型的列

SetDataTypeInitPhase1 函数

  SetDataTypeInitPhase1 函数的主要目的是为数据类型变更操作的第一阶段准备必要的数据结构和资源。这包括为每个受影响的列分配存储其信息的空间读写操作所需的存储结构执行最小/最大值检查的函数,以及足够的内存来处理和转换数据。如果表空间发生了变化,还需要为数据的追加写入位置分配额外的偏移量。这个初始化阶段是确保数据类型变更能顺利进行的关键步骤,它为数据的读取、处理和重新存储奠定了基础。通过这样的准备,系统能够有效地处理数据类型的更改,保证数据的一致性和完整性。函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.cpp

void CStoreRewriter::SetDataTypeInitPhase1()
{// 确保需要设置数据类型的列的数量大于0Assert(m_SDTColsNum > 0);// 为需要设置数据类型的列分配指针数组,存储每列的相关信息m_SDTColsInfo = (CStoreRewriteColumn**)palloc(sizeof(CStoreRewriteColumn*) * m_SDTColsNum);// 为每个列分配读取存储对象的数组,用于从列存储中读取数据m_SDTColsReader = (CUStorage**)palloc(sizeof(CUStorage*) * m_SDTColsNum);// 为每个列分配函数指针数组,用于执行最小/最大值函数,这对查询优化非常重要m_SDTColsMinMaxFunc = (FuncSetMinMax*)palloc(sizeof(FuncSetMinMax) * m_SDTColsNum);// 为每个列分配写入存储对象的数组,用于将处理后的数据写回列存储m_SDTColsWriter = (CUStorage**)palloc(sizeof(CUStorage*) * m_SDTColsNum);// 分配足够存储一个完整列存储单元最大数据量的值数组m_SDTColValues = (Datum*)palloc(sizeof(Datum) * RelMaxFullCuSize);// 分配足够存储一个完整列存储单元最大数据量的布尔数组,用于标记值是否为NULLm_SDTColIsNull = (bool*)palloc(sizeof(bool) * RelMaxFullCuSize);// 如果表空间发生变化,为每个列分配新的追加偏移量数组if (m_TblspcChanged) {m_SDTColAppendOffset = (CUPointer*)palloc(sizeof(CUPointer) * m_SDTColsNum);}
}

SetDataTypeInitPhase2 函数

  SetDataTypeInitPhase2 函数主要负责在数据类型变更过程中,为每个受影响的列配置具体的读写存储、设置最小/最大值函数、以及处理表空间变化带来的文件存储变更。函数首先更新列的信息对象和最小/最大值函数,然后根据表空间是否变更决定如何设置列的文件存储策略。如果表空间未变更,则使用原有的文件节点;如果表空间变更,则创建新的文件节点并设置为追加模式。此外,函数还确保在进行数据类型变更时注册相关的操作,以便系统能跟踪这些变更。通过这些步骤,函数确保每个列的数据存储策略和管理策略得到妥善设置,从而支持高效且安全的数据类型变更操作。函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.cpp

void CStoreRewriter::SetDataTypeInitPhase2(_in_ CStoreRewriteColumn* sdtColInfo, _in_ int idx)
{// 将传入的列信息对象存储在指定的数组索引位置m_SDTColsInfo[idx] = sdtColInfo;/* 为变更数据类型的列设置最小/最大值函数 */// 根据新的数据类型获取并设置相应的最小/最大值函数m_SDTColsMinMaxFunc[idx] = GetMinMaxFunc(m_NewTupDesc->attrs[sdtColInfo->attrno - 1].atttypid);// 根据原始关系文件节点和列号创建文件节点对象,用于读取操作CFileNode cFileNode(m_OldHeapRel->rd_node, (int)sdtColInfo->attrno, MAIN_FORKNUM);m_SDTColsReader[idx] = New(CurrentMemoryContext) CUStorage(cFileNode);/* 当表空间未变化时:* 1. 不创建新的列存储单元文件,而是重用现有文件。* 2. 覆盖空闲空间或直接追加。** 表空间可能发生变化,因此创建新的文件节点对象*/RelFileNode wrtRelFileNode = m_OldHeapRel->rd_node;wrtRelFileNode.spcNode = m_TargetTblspc; // 设置目标表空间wrtRelFileNode.relNode = m_TargetRelFileNode; // 设置目标文件节点CFileNode wrtCFileNode(wrtRelFileNode, (int)sdtColInfo->attrno, MAIN_FORKNUM);m_SDTColsWriter[idx] = New(CurrentMemoryContext) CUStorage(wrtCFileNode);// 如果表空间已变更if (m_TblspcChanged) {// 创建新的列存储单元文件,并记录日志m_SDTColsWriter[idx]->CreateStorage(0, false);CStoreRelCreateStorage(&wrtCFileNode.m_rnode,sdtColInfo->attrno,m_OldHeapRel->rd_rel->relpersistence,m_OldHeapRel->rd_rel->relowner);// 采用仅追加策略m_SDTColAppendOffset[idx] = 0;// 记录日志信息ereport(LOG,(errmsg("Column [SET TYPE]: %s(%u C%d) tblspc %u/%u/%u => %u/%u/%u",RelationGetRelationName(m_OldHeapRel),RelationGetRelid(m_OldHeapRel),(sdtColInfo->attrno - 1),m_OldHeapRel->rd_node.spcNode,m_OldHeapRel->rd_node.dbNode,m_OldHeapRel->rd_node.relNode,m_TargetTblspc,m_OldHeapRel->rd_node.dbNode,m_TargetRelFileNode)));} else {// 设置列分配策略为仅追加m_SDTColsWriter[idx]->SetAllocateStrategy(APPEND_ONLY);// 构建追加策略下的空间缓存CStoreAllocator::BuildColSpaceCacheForRel(m_OldHeapRel, &m_SDTColsInfo[idx]->attrno, 1);}// 注册需要变更数据类型的关系标识CStoreAlterRegister* alterReg = GetCstoreAlterReg();alterReg->Add(RelationGetRelid(m_OldHeapRel));
}

SetDataTypeDestroy 函数

  SetDataTypeDestroy 函数是数据类型变更过程中资源管理的关键部分,它确保在数据类型设置完成后,所有分配的内存和资源被适当地释放。这包括释放用于读取和写入数据的存储对象列信息数组最小/最大值函数数组列值数组列值 NULL 标记数组,以及表空间变更时使用的追加偏移量数组。释放这些资源是防止内存泄露和维护系统稳定性的重要措施。
  通过逐项释放在数据类型变更操作中创建的所有动态分配的资源,此函数帮助确保系统资源得到有效管理,避免因资源占用过多而影响系统性能。这种资源清理策略是长时间运行的系统和服务中常见的良好实践,特别是在涉及大量数据处理和频繁修改表结构的数据库管理系统中。函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.cpp

// 如果已分配资源(即有列的数据类型需要被设置)
// free all the unused resource after SET DATA TYPE.
void CStoreRewriter::SetDataTypeDestroy()
{// 如果已分配资源(即有列的数据类型需要被设置)if (m_SDTColsNum) {// 遍历每个数据类型变更的列for (int i = 0; i < m_SDTColsNum; ++i) {// 删除并释放每列的读取存储对象DELETE_EX(m_SDTColsReader[i]);// 删除并释放每列的写入存储对象DELETE_EX(m_SDTColsWriter[i]);}// 释放存储列信息的数组pfree_ext(m_SDTColsInfo);// 释放存储列读取对象指针的数组pfree_ext(m_SDTColsReader);// 释放存储最小/最大函数指针的数组pfree_ext(m_SDTColsMinMaxFunc);// 释放存储列写入对象指针的数组pfree_ext(m_SDTColsWriter);// 释放存储列值的数组pfree_ext(m_SDTColValues);// 释放存储列值是否为NULL的布尔数组pfree_ext(m_SDTColIsNull);// 如果表空间发生了变化,还需要释放存储追加偏移量的数组if (m_TblspcChanged) {pfree_ext(m_SDTColAppendOffset);}}
}

BeginRewriteCols 函数

  BeginRewriteCols 函数负责初始化列重写操作的准备工作,包括为新增列和数据类型更改的列设置相关的初始化环节。此方法首先验证是否有列需要重写,然后根据具体的需求(如添加新列或更改数据类型),执行相应的初始化步骤。它确保了在实际执行重写操作前,所有必要的数据结构和管理策略都已就绪
  此外,此方法还负责创建和配置新的列描述符关系cudesc relation),这是管理列存储表结构变更的关键部分。通过这样的设置,CStoreRewriter 能够高效且安全地处理复杂的表结构变更操作,确保数据的一致性和系统的稳定性。

void CStoreRewriter::BeginRewriteCols(_in_ int nRewriteCols, _in_ CStoreRewriteColumn** pRewriteCols,_in_ const int* rewriteColsNum, _in_ bool* rewriteFlags)
{// 确认传入的重写列数与新元组描述的属性数匹配Assert(nRewriteCols == m_NewTupDesc->natts);// 设置需要添加的列的数量m_AddColsNum = rewriteColsNum[CSRT_ADD_COL];// 设置需要更改数据类型的列的数量m_SDTColsNum = rewriteColsNum[CSRT_SET_DATA_TYPE];// 存储每列是否需要重写的标志m_ColsRewriteFlag = rewriteFlags;// 如果没有列需要重写,直接返回if (!NeedRewrite())return;// 如果有列需要添加,执行添加列的初始化第一阶段if (m_AddColsNum > 0) {AddColumnInitPhrase1();}// 如果有列需要数据类型变更,执行设置数据类型的初始化第一阶段if (m_SDTColsNum > 0) {SetDataTypeInitPhase1();}// 初始化添加列和数据类型变更的计数器int nAddCols = 0;int nChangeCols = 0;// 遍历所有需要重写的列for (int i = 0; i < nRewriteCols; ++i) {// 如果当前列不需要重写,跳过if (pRewriteCols[i] == NULL) {Assert(!rewriteFlags[i]);continue;}// 断言当前列确实需要重写Assert(rewriteFlags[i]);// 如果当前列是新添加的,执行添加列的初始化第二阶段if (pRewriteCols[i]->isAdded) {AddColumnInitPhrase2(pRewriteCols[i], nAddCols);++nAddCols;} else if (!pRewriteCols[i]->isDropped) { // 如果当前列是需要更改数据类型的SetDataTypeInitPhase2(pRewriteCols[i], nChangeCols);++nChangeCols;} else/* description: future plan-dropped columns case */;}// 断言添加的列和数据类型变更的列的数量与预期相符Assert(nAddCols == m_AddColsNum);Assert(nChangeCols == m_SDTColsNum);// 为重写创建新的列描述符关系Oid oldCuDescHeap = m_OldHeapRel->rd_rel->relcudescrelid;m_NewCuDescHeap = make_new_heap(oldCuDescHeap, m_TargetTblspc);// 打开新的列描述符关系,以独占方式访问m_NewCudescRel = heap_open(m_NewCuDescHeap, AccessExclusiveLock);// 创建用于批量插入新列描述符的对象m_NewCudescBulkInsert = New(CurrentMemoryContext) HeapBulkInsert(m_NewCudescRel);
}

  下面是 CStoreRewriter::BeginRewriteCols 函数的执行流程,使用中文伪代码的形式描述:

函数 BeginRewriteCols(输入: nRewriteCols, pRewriteCols, rewriteColsNum, rewriteFlags)断言: nRewriteCols 应等于 m_NewTupDesc 的属性数// 初始化需要添加的列数和需要更改数据类型的列数m_AddColsNum = rewriteColsNum[添加列的索引]m_SDTColsNum = rewriteColsNum[设置数据类型的索引]m_ColsRewriteFlag = rewriteFlags// 检查是否需要进行重写操作如果 不需要重写:返回// 如果有列需要添加, 执行添加列的初始化第一阶段如果 m_AddColsNum > 0:执行 AddColumnInitPhrase1()// 如果有列需要更改数据类型, 执行设置数据类型的初始化第一阶段如果 m_SDTColsNum > 0:执行 SetDataTypeInitPhase1()// 初始化添加列和更改数据类型的计数器初始化 nAddCols = 0初始化 nChangeCols = 0// 遍历所有需要重写的列对于 i 从 0 到 nRewriteCols:// 如果当前列不需要重写, 跳过此列如果 pRewriteCols[i] 是空:断言 rewriteFlags[i] 为假继续下一次循环// 断言当前列确实标记为需要重写断言 rewriteFlags[i] 为真// 根据列的状态执行相应的初始化如果 pRewriteCols[i] 是新增的:执行 AddColumnInitPhrase2(pRewriteCols[i], nAddCols)nAddCols 自增否则如果 pRewriteCols[i] 没有被丢弃:执行 SetDataTypeInitPhase2(pRewriteCols[i], nChangeCols)nChangeCols 自增否则:// 处理已丢弃的列的情况(未来计划)// 断言添加和变更的列数与预期一致断言 nAddCols 等于 m_AddColsNum断言 nChangeCols 等于 m_SDTColsNum// 为重写创建新的列描述符关系获取旧的列描述符关系IDm_NewCuDescHeap = 创建新的堆(oldCuDescHeap, m_TargetTblspc)m_NewCudescRel = 打开新的列描述符关系(m_NewCuDescHeap, 独占访问)m_NewCudescBulkInsert = 创建新的批量插入对象(m_NewCudescRel)结束函数

RewriteColsData 函数

  RewriteColsData 函数主要负责执行列存储表中列数据的重写操作。这个函数的执行流程首先检查是否需要进行重写,如果不需要,则直接返回;否则,它将遍历所有的列存储单元(CU),并对每个CU进行处理。该函数通过系统扫描来检索与每个 CU 关联的元组信息,这包括既有数据的修改虚拟删除标记的处理。对于需要添加新列或者需要数据类型变更的列,函数会执行相应的初始化和设置过程,确保数据的正确迁移和更新。
  在数据重写过程中,函数会处理可能存在的 TOAST 数据,确保这些数据在新旧表之间正确转移,同时还会通过内容交换来处理 TOAST 表数据。该函数还管理了大量的内存操作,包括使用临时内存上下文来优化内存使用效率,并在每次迭代结束时重置这些内存上下文,以避免内存泄漏。
  一旦完成所有数据的处理,RewriteColsData 将所有更改刷新到磁盘,并关闭与操作相关的数据库关系和索引。此外,它还负责清理所有的临时资源,包括批量插入对象和内存上下文,以确保系统资源的合理使用和释放。最终,通过日志记录关键的操作细节,提供对过程的审计和追踪,增强了系统操作的透明度和可追溯性。

void CStoreRewriter::RewriteColsData()
{// 检查是否需要进行列重写,如果不需要则直接退出函数if (!NeedRewrite())return;// 设置第一个有效的CU ID,通常是第一个CU ID加1uint32 nextCuId = FirstCUID + 1;// 获取关联到旧表的CU描述符的OIDOid oldCudescOid = m_OldHeapRel->rd_rel->relcudescrelid;// 获取当前最大的CU IDuint32 maxCuId = CStore::GetMaxCUID(oldCudescOid, m_OldTupDesc);// 打开旧的CU描述符关系表,并加上独占锁Relation oldCudescHeap = heap_open(oldCudescOid, AccessExclusiveLock);// 打开旧的CU描述符索引关系表,并加上独占锁Relation oldCudescIndex = index_open(oldCudescHeap->rd_rel->relcudescidx, AccessExclusiveLock);// 获取旧的CU描述符的元组描述符TupleDesc oldCudescTupDesc = oldCudescHeap->rd_att;// 获取旧的CU描述符关系表的TOAST表的OIDOid oldCudescToastId = oldCudescHeap->rd_rel->reltoastrelid;// 初始化布尔变量,用于后续检查是否值为NULLbool isnull = false;// 初始化ScanKey数组,用于数据库扫描操作ScanKeyData key[ARRAY_2_LEN];// 获取旧元组描述的属性数量int nOldAttrs = m_OldTupDesc->natts;int rc = 0;// 分配内存存储CU描述元组int const cudescTupsLen = sizeof(HeapTuple) * nOldAttrs;HeapTuple* cudescTups = (HeapTuple*)palloc(cudescTupsLen);HeapTuple virtualDelTup = NULL;HeapTuple tup = NULL;// 创建一个循环内存上下文,用于数据重写过程中的内存管理MemoryContext oneLoopMemCnxt = AllocSetContextCreate(CurrentMemoryContext,"Cstore Rewriting Memory",ALLOCSET_DEFAULT_MINSIZE,ALLOCSET_DEFAULT_INITSIZE,ALLOCSET_DEFAULT_MAXSIZE);// 遍历所有的CU ID,直到达到最大CU IDwhile (nextCuId <= maxCuId) {// 检查是否有中断发生,如果有,处理中断CHECK_FOR_INTERRUPTS();// 重置之前循环使用的内存,清空上一次的数据MemoryContextReset(oneLoopMemCnxt);// 将CU描述元组数组清零,准备新的数据加载rc = memset_s(cudescTups, cudescTupsLen, 0, cudescTupsLen);securec_check(rc, "", "");virtualDelTup = NULL;tup = NULL;// 切换到私有内存上下文,为数据加载和处理准备环境AutoContextSwitch newMemCnxt(oneLoopMemCnxt);int scannedTuples = 0;// 初始化ScanKey,设置列ID和CU ID的扫描键ScanKeyInit(&key[0], (AttrNumber)CUDescColIDAttr, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(1));ScanKeyInit(&key[1], (AttrNumber)CUDescCUIDAttr, BTEqualStrategyNumber, F_OIDEQ, UInt32GetDatum(nextCuId));// 遍历所有的属性,跳过已删除的列for (int i = 0; i < nOldAttrs; i++) {if (unlikely(m_OldTupDesc->attrs[i].attisdropped))continue;key[0].sk_argument = Int32GetDatum(i + 1);/** 开始有序的系统扫描* 如果一些现有的列存储单元(CU)数据将被重写,* 那么已删除的元组将被跳过,并可能简单地标记为 NULL。* 因此,需要记住具有相同 CU ID 的所有元组以及虚拟删除的元组。*/SysScanDesc oldCuDescScan = systable_beginscan_ordered(oldCudescHeap, oldCudescIndex, SnapshotNow, ARRAY_2_LEN, key);// 如果找到匹配的元组,则复制并保存if ((tup = systable_getnext_ordered(oldCuDescScan, ForwardScanDirection)) != NULL) {Assert((i + 1) == DatumGetInt32(fastgetattr(tup, CUDescColIDAttr, oldCudescTupDesc, &isnull)));Assert(!isnull);Assert(nextCuId == DatumGetUInt32(fastgetattr(tup, CUDescCUIDAttr, oldCudescTupDesc, &isnull)));Assert(!isnull);Assert(scannedTuples < nOldAttrs);// 必须复制此元组,因为稍后将在WHILE循环之外访问它。cudescTups[i] = heap_copytuple(tup);++scannedTuples;}systable_endscan_ordered(oldCuDescScan);oldCuDescScan = NULL;}// 如果没有扫描到元组,意味着这个CU ID没有数据,继续下一个IDif (0 == scannedTuples) {++nextCuId;continue;}// 设置查找键的参数为虚拟删除列ID,准备进行系统扫描key[0].sk_argument = Int32GetDatum(VitrualDelColID);// 开始有序的系统扫描,使用当前快照查找与上述键匹配的元组oldCuDescScan = systable_beginscan_ordered(oldCudescHeap, oldCudescIndex, SnapshotNow, ARRAY_2_LEN, key);// 如果能在系统扫描中找到对应的元组if ((tup = systable_getnext_ordered(oldCuDescScan, ForwardScanDirection)) != NULL) {// 确保之前没有找到过虚拟删除的元组(保证唯一性)Assert(virtualDelTup == NULL);// 复制找到的元组,用于后续处理virtualDelTup = heap_copytuple(tup);}// 结束系统扫描systable_endscan_ordered(oldCuDescScan);// 清空扫描指针,准备下次使用或释放oldCuDescScan = NULL;// 如果没有找到虚拟删除的元组,抛出错误if (virtualDelTup == NULL) {Assert(false);ereport(ERROR,(errcode(ERRCODE_NO_DATA),errmsg("Relation \'%s\' virtual cudesc tuple(cuid %u) not found",RelationGetRelationName(m_OldHeapRel),nextCuId)));}// 从虚拟删除的元组中获取行数属性的值uint32 rowCount = DatumGetUInt32(fastgetattr(virtualDelTup, CUDescRowCountAttr, oldCudescTupDesc, &isnull));// 确保获取行数属性值时没有发生错误Assert(!isnull);// 初始化指向删除掩码数据的指针char* delMaskDataPtr = NULL;// 初始化整个CU是否被删除的标志bool wholeCuIsDeleted = false;// 从虚拟删除的元组中获取指向删除掩码的数据指针char* delMaskVarDatum =DatumGetPointer(fastgetattr(virtualDelTup, CUDescCUPointerAttr, oldCudescTupDesc, &isnull));// 如果删除成功掩码数据存在if (!isnull) {// 解压可能被压缩的TOAST数据delMaskVarDatum = (char*)PG_DETOAST_DATUM(delMaskVarDatum);// 验证解压后的数据大小是否符合预期Assert(VARSIZE_ANY_EXHDR(delMaskVarDatum) == bitmap_size(rowCount));// 获取删除掩码数据的实际指针delMaskDataPtr = VARDATA_ANY(delMaskVarDatum);// 判断整个CU是否被删除wholeCuIsDeleted = CStore::IsTheWholeCuDeleted(delMaskDataPtr, rowCount);}// 处理添加列的操作AddColumns(nextCuId, rowCount, wholeCuIsDeleted);// 处理数据类型更改操作SetDataType(nextCuId, cudescTups, oldCudescTupDesc, delMaskDataPtr, rowCount, wholeCuIsDeleted);// 将剩余的CU描述元组和虚拟删除元组插入到CU描述表中for (int i = 0; i < nOldAttrs; ++i) {if (m_OldTupDesc->attrs[i].attisdropped || m_NewTupDesc->attrs[i].attisdropped)continue;if (!m_ColsRewriteFlag[i]) {m_NewCudescBulkInsert->BulkInsertCopy(cudescTups[i]);}}// 最后复制虚拟删除的元组m_NewCudescBulkInsert->EnterBulkMemCnxt();m_NewCudescBulkInsert->BulkInsert(CStore::FormVCCUDescTup(oldCudescTupDesc, delMaskDataPtr, nextCuId, rowCount, GetCurrentTransactionIdIfAny()));m_NewCudescBulkInsert->LeaveBulkMemCnxt();++nextCuId;}// 刷新所有列存储(CU)数据,确保所有更改都被持久化到磁盘FlushAllCUData();// 完成批量插入操作,确保所有插入的数据都已提交m_NewCudescBulkInsert->Finish();// 删除批量插入对象,释放相关资源DELETE_EX(m_NewCudescBulkInsert);// 删除用于数据重写的临时内存上下文,清理过程中使用的临时内存MemoryContextDelete(oneLoopMemCnxt);// 释放存储列描述元组的内存pfree_ext(cudescTups);// 如果表空间发生了变更,记录相应的日志if (m_TblspcChanged) {ereport(LOG,(errmsg("Row [Rewrite]: %s(%u) tblspc %u/%u/%u => %u/%u/%u",RelationGetRelationName(oldCudescHeap),RelationGetRelid(oldCudescHeap),oldCudescHeap->rd_node.spcNode,oldCudescHeap->rd_node.dbNode,oldCudescHeap->rd_node.relNode,m_NewCudescRel->rd_node.spcNode,m_NewCudescRel->rd_node.dbNode,m_NewCudescRel->rd_node.relNode)));}// 重置新CU描述关系的TOAST表OID,清理前的整理工作m_NewCudescRel->rd_toastoid = InvalidOid;// 关闭新的CU描述关系,释放锁heap_close(m_NewCudescRel, NoLock);// 将指针设为NULL,避免悬挂引用m_NewCudescRel = NULL;// 关闭旧的CU描述关系,释放锁heap_close(oldCudescHeap, NoLock);// 关闭旧的CU描述索引关系,释放锁index_close(oldCudescIndex, NoLock);// 完成重写CU描述关系的过程,交换旧的CU描述表和新的CU描述表finish_heap_swap(m_OldHeapRel->rd_rel->relcudescrelid, m_NewCuDescHeap, false,swapToastByContent, false, m_NewCudescFrozenXid, FirstMultiXactId);}

EndRewriteCols 函数

  EndRewriteCols 函数是用于在列重写操作完成后执行的清理工作。此函数首先检查是否存在需要重写的列,如果没有,则立即退出函数。这种检查是为了确保不进行不必要的清理操作,从而优化性能。
  如果存在需要重写的列,函数将继续执行两个重要的清理步骤:AddColumnDestroySetDataTypeDestroy。这两个函数分别负责清理在列添加和数据类型更改过程中创建的各种资源。AddColumnDestroy 主要用于释放添加新列时分配的内存和其他资源,而 SetDataTypeDestroy 用于清理在数据类型变更过程中分配的资源
  通过这样的设计,EndRewriteCols 函数确保在重写列的操作完全结束后,所有的临时资源都得到了妥善的管理和释放,从而维护了系统的稳定性和高效性。这也有助于防止内存泄漏,确保数据库在长时间运行后仍能保持良好的性能。函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.cpp

void CStoreRewriter::EndRewriteCols()
{// 将列重写标志数组的指针设置为NULL,表示不再引用这个数组m_ColsRewriteFlag = NULL;// 如果当前没有需要重写的列,则直接返回if (!NeedRewrite())return;// 执行清理工作,结束列重写操作// 调用 AddColumnDestroy 函数来清理与添加列相关的资源AddColumnDestroy();// 调用 SetDataTypeDestroy 函数来清理与设置数据类型相关的资源SetDataTypeDestroy();
}

AddColumnDestroy 函数

  AddColumnDestroy 函数的作用是在完成添加新列的操作后,释放在此过程中分配的所有资源。这包括释放用于存储列信息列存储对象列的最小/最大值函数以及列的追加偏移量等资源的内存。此函数确保了在列添加操作后不会有资源泄漏,维护了系统的健康和性能。
  在数据库的列存储系统中,添加列可能涉及到复杂的内部操作,包括数据的分配和内存管理。通过在操作完成后调 AddColumnDestroy,系统能够回收所有相关资源,从而避免不必要的内存占用和潜在的性能问题。这种资源管理策略是保持数据库长时间高效运行的关键部分。函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.cpp

// free all the unused resource after ADD COLUMN.
void CStoreRewriter::AddColumnDestroy()
{// 如果有添加的列(即添加列数量大于0)if (m_AddColsNum > 0) {// 遍历所有添加的列for (int i = 0; i < m_AddColsNum; ++i) {// 使用 DELETE_EX 宏来安全地删除每个列的存储对象,并释放相关内存DELETE_EX(m_AddColsStorage[i]);}// 释放存储列信息指针的内存pfree_ext(m_AddColsInfo);// 释放存储列存储对象指针的内存pfree_ext(m_AddColsStorage);// 释放存储最小/最大值函数指针的内存pfree_ext(m_AddColsMinMaxFunc);// 释放存储追加偏移量的内存pfree_ext(m_AddColsAppendOffset);}
}

SetDataTypeDestroy 函数

  SetDataTypeDestroy 函数在完成列的数据类型设置后执行,目的是释放在该过程中分配的所有资源。这包括对每个列的读写存储对象、列信息、最小最大值函数、中间数据值、null标志以及追加偏移量(如果表空间发生变化时分配的)等资源的释放。
  在数据库的列存储结构中,更改列的数据类型是一个复杂的操作,涉及到多种资源的分配和管理。SetDataTypeDestroy 通过系统地释放这些资源,确保了内存的有效管理,防止了资源泄漏,从而帮助维护了系统的稳定性和性能。这个清理函数是确保系统在完成重要操作后能够回到一个干净、稳定状态的关键步骤,特别是在频繁修改表结构的环境中尤为重要。函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.cpp

// free all the unused resource after SET DATA TYPE.
void CStoreRewriter::SetDataTypeDestroy()
{// 如果有进行数据类型更改的列if (m_SDTColsNum) {// 遍历所有需要更改数据类型的列for (int i = 0; i < m_SDTColsNum; ++i) {// 使用 DELETE_EX 宏安全地删除和释放每个列的读取存储对象DELETE_EX(m_SDTColsReader[i]);// 使用 DELETE_EX 宏安全地删除和释放每个列的写入存储对象DELETE_EX(m_SDTColsWriter[i]);}// 释放存储列信息的内存pfree_ext(m_SDTColsInfo);// 释放存储列读取对象指针的内存pfree_ext(m_SDTColsReader);// 释放存储最小/最大值函数指针的内存pfree_ext(m_SDTColsMinMaxFunc);// 释放存储列写入对象指针的内存pfree_ext(m_SDTColsWriter);// 释放存储数据类型转换中间值的内存pfree_ext(m_SDTColValues);// 释放存储数据是否为null的标志的内存pfree_ext(m_SDTColIsNull);// 如果表空间发生了变化,也释放追加偏移量的内存if (m_TblspcChanged) {pfree_ext(m_SDTColAppendOffset);}}
}

处理 Cu 相关函数

HandleCuWithSameValue 函数

  HandleCuWithSameValue 函数用于处理具有相同值的列更新。它首先检查是否需要更新列存储单元CU),如果需要,它会创建一个新的 CU 实例,快速生成新的 CU 数据,然后根据传入的 append 参数决定是追加数据还是使用现有空间。数据压缩后会被写入磁盘,并且相关的 CU 描述信息会被更新。如果是追加模式CU 描述的指针会被更新到新的偏移位置。如果操作涉及高可用性复制,更新的数据也会被推送到复制队列。此函数确保数据的一致性和高效处理,是列存储管理中的一个关键环节,尤其在处理大量具有相同值的数据时显得尤为重要。函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.cpp

// 函数模板定义,用于处理具有相同值的列存储单元(CU)更新操作
template <bool append>
void CStoreRewriter::HandleCuWithSameValue(_in_ Relation rel, _in_ Form_pg_attribute pColAttr, _in_ Datum colValue,_in_ bool colIsNull, _in_ FuncSetMinMax MinMaxFunc, _in_ CUStorage* colStorage, __inout CUDesc* pColCudescData,__inout CUPointer* pOffset)
{// 步骤1: 设置CU描述的模式。如果返回true,说明需要将新数据写入CU文件。if (CStore::SetCudescModeForTheSameVal(colIsNull, MinMaxFunc, pColAttr->attlen, colValue, pColCudescData)) {// 创建新的CU对象CU* cuPtr = New(CurrentMemoryContext) CU(pColAttr->attlen, pColAttr->atttypmod, pColAttr->atttypid);// 步骤2: 快速形成新的CU数据。CStoreRewriter::FormCuDataForTheSameVal(cuPtr, pColCudescData->row_count, colValue, pColAttr);// 步骤3: 在内存中压缩CU数据,并准备将它们写入CU文件。int16 compressing_modes = 0;heaprel_set_compressing_modes(rel, &compressing_modes);CStoreRewriter::CompressCuData(cuPtr, pColCudescData, pColAttr, compressing_modes);// 步骤4: 为这个CU数据分配文件空间。if (append) {// 如果是追加模式,则更新CU描述的指针并调整偏移量。pColCudescData->cu_pointer = *pOffset;*pOffset += pColCudescData->cu_size;} else {// 如果不是追加模式,从列存储中分配空间。Assert(pOffset == NULL);pColCudescData->cu_pointer = colStorage->AllocSpace(pColCudescData->cu_size);}// 步骤5: 将CU数据保存到磁盘文件中。CStoreRewriter::SaveCuData(cuPtr, pColCudescData, colStorage);// 步骤6: 关于高可用性,将CU数据推送到复制队列中CStoreCUReplication((this->m_TblspcChanged ? this->m_CUReplicationRel : rel),pColAttr->attnum,cuPtr->m_compressedBuf,pColCudescData->cu_size,pColCudescData->cu_pointer);// 删除CU对象DELETE_EX(cuPtr);} else {// 如果不需要写入新数据,则确认CU描述的大小和指针都为0Assert(pColCudescData->cu_size == 0);Assert(pColCudescData->cu_pointer == 0);}
}

  以下是该函数执行流程的伪代码:

函数 HandleCuWithSameValue(append, rel, pColAttr, colValue, colIsNull, MinMaxFunc, colStorage, pColCudescData, pOffset):如果 SetCudescModeForTheSameVal 返回 true:创建一个新的 CU 对象 cuPtr调用 FormCuDataForTheSameVal 为相同值形成新的 CU 数据获取压缩模式 compressing_modes调用 CompressCuData 压缩 CU 数据如果 append 为 true:设置 pColCudescData 的 cu_pointer 为 pOffset更新 pOffset 为下一次写入的位置否则:断言 pOffset 为空分配文件空间给该 CU 数据并将其存储在 colStorage 中调用 SaveCuData 将 CU 数据保存到文件中如果需要 HA:调用 CStoreCUReplication 将 CU 数据推送到复制队列删除 cuPtr否则:断言 pColCudescData 的 cu_size 和 cu_pointer 均为 0

FormCuDataForTheSameVal 函数

  FormCuDataForTheSameVal 函数根据给定的新列值列属性和行数,在内存中为相同值的列单元CU)形成新的 CU 数据。首先,它计算新列值的大小,然后计算出初始化大小,接着检查初始化大小是否溢出。如果没有溢出,则初始化 CU,并追加数据;否则,报告错误,说明列的默认值大小过大。
  该函数与 HandleCuWithSameValue 函数的关系是:HandleCuWithSameValue 函数是一个更高级别的函数,它调用了 FormCuDataForTheSameVal 函数来为相同值的列单元形成新的 CU 数据HandleCuWithSameValue 函数负责处理整个 CU 的流程,而 FormCuDataForTheSameVal 函数则负责具体的 CU 数据形成过程。函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.cpp

// 为相同值形成新的 CU 数据。
void CStoreRewriter::FormCuDataForTheSameVal(CU* cuPtr, int rowsCntInCu, Datum newColVal, Form_pg_attribute newColAttr)
{// 计算新列值的大小。const Size valSize = datumGetSize(newColVal, newColAttr->attbyval, newColAttr->attlen);// 计算初始化大小,不跳过已删除的堆元组。const Size initSize = valSize * rowsCntInCu;// 检查 *initSize* 是否溢出。if (initSize < UINT32_MAX) {// 到这里,安全地将 *initSize* 转换为 uint32。cuPtr->InitMem((uint32)initSize, rowsCntInCu, false);// 追加 CU 数据。CU::AppendCuData(newColVal, rowsCntInCu, newColAttr, cuPtr);return;}// 报告错误,因为列的默认值大小过大。ereport(ERROR,(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),errmsg("column \"%s\" needs too many memory", NameStr(newColAttr->attname)),errdetail("its size of default value is too big.")));
}

CompressCuData 函数

  CompressCuData 函数的作用是在重写 CU 文件期间压缩 CU 数据。它接收压缩模式值CU 描述CU 对象新列的属性作为输入,并使用临时的压缩选项和 cu_tmp_compress_info 来设置 CU 的临时信息。然后,它设置 CUmagic 值,并对 CU 数据进行压缩,最后获取压缩后的 CU 大小。
  该函数与 HandleCuWithSameValue 函数的关系是:HandleCuWithSameValue 函数调用了 CompressCuData 函数来CU 数据进行压缩。在处理相同值的列单元时,需要对数据进行压缩以节省存储空间。 CompressCuData 函数负责具体的压缩过程。函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.cpp

/** @Description: 压缩 CU 数据* @IN/ compressing_modes: 压缩模式值* @IN/OUT cuDesc: CU 描述* @IN/OUT cuPtr: CU 对象* @IN newColAttr: 新列的属性* @See also: 无*/
void CStoreRewriter::CompressCuData(CU* cuPtr, CUDesc* cuDesc, Form_pg_attribute newColAttr, int16 compressing_modes)
{// 在重写 CU 文件期间,我们将使用临时的压缩选项和 cu_tmp_compress_infocompression_options tmp_filter;tmp_filter.reset();cu_tmp_compress_info cu_temp_info;cu_temp_info.m_options = &tmp_filter;// 如果不需要重新计算最小值和最大值,则设置 m_valid_minmax 为 truecu_temp_info.m_valid_minmax = !NeedToRecomputeMinMax(newColAttr->atttypid);if (cu_temp_info.m_valid_minmax) {// 如果 m_valid_minmax 有效,则设置最小值和最大值cu_temp_info.m_min_value = ConvertToInt64Data(cuDesc->cu_min, newColAttr->attlen);cu_temp_info.m_max_value = ConvertToInt64Data(cuDesc->cu_max, newColAttr->attlen);}// 将 cuPtr 的临时信息设置为 cu_temp_infocuPtr->m_tmpinfo = &cu_temp_info;// 设置 CU 的魔术值cuPtr->SetMagic(cuDesc->magic);// 压缩 CU 数据cuPtr->Compress(cuDesc->row_count, compressing_modes, ALIGNOF_CUSIZE);// 获取压缩后的 CU 大小cuDesc->cu_size = cuPtr->GetCUSize();// 断言 CU 大小大于 0Assert(cuDesc->cu_size > 0);
}

SetDataTypeHandleFullNullCu 函数

  SetDataTypeHandleFullNullCu 函数的作用是处理全空(FULL NULL)的列单元的数据类型。它接收一个原列描述符 oldColCudesc,并将其复制到新的列描述符 newColCudesc 中,然后将新的列描述符的 magic 字段更新为当前事务 ID(如果存在)。在处理全空的列单元时,由于数据本身为空,所以只需更新描述符的元数据信息。函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.cpp

FORCE_INLINE void CStoreRewriter::SetDataTypeHandleFullNullCu(_in_ CUDesc* oldColCudesc, _out_ CUDesc* newColCudesc)
{// 断言原列描述符是一个全空(FULL NULL)的列单元。Assert(oldColCudesc->IsNullCU());// 断言原列描述符不是一个相同值(SAME VALUE)的列单元。Assert(!oldColCudesc->IsSameValCU());// 如果原列描述符是一个全空(FULL NULL)的列单元,则无需操作。// 这里只需要更新magic字段。// 将新列描述符设置为与原列描述符相同的值,并更新magic字段为当前事务ID(如果存在)。*newColCudesc = *oldColCudesc;newColCudesc->magic = GetCurrentTransactionIdIfAny();
}

SetDataTypeHandleSameValCu函数

  SetDataTypeHandleSameValCu 函数的作用是处理相同值(SAME VALUE)的列单元的数据类型。它接收一个列描述符 oldColCudesc,并根据其中的信息来计算新的列单元的数据。首先,它获取要处理的列的相关信息,并构造一个虚假的元组以便计算新值。然后,根据计算得到的新值,调用 HandleCuWithSameValue 函数处理相同值的列单元,以更新数据。函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.cpp

/** @Description: 处理相同值的列单元的数据类型,根据新值计算并更新 CU 数据* @IN sdtIndex: 设置数据类型的列索引* @IN oldColCudesc: 原列描述符* @OUT newColCudesc: 新列描述符* @See also: 无*/
void CStoreRewriter::SetDataTypeHandleSameValCu(_in_ int sdtIndex, _in_ CUDesc* oldColCudesc, _out_ CUDesc* newColCudesc)
{// 断言原列描述符不是一个全空(NULL)的列单元。Assert(!oldColCudesc->IsNullCU());// 断言原列描述符是一个相同值(SAME VALUE)的列单元。Assert(oldColCudesc->IsSameValCU());// 将新列描述符的 cu_id、row_count 字段设置为与原列描述符相同的值,并将magic字段更新为当前事务ID(如果存在)。newColCudesc->cu_id = oldColCudesc->cu_id;newColCudesc->row_count = oldColCudesc->row_count;newColCudesc->magic = GetCurrentTransactionIdIfAny();// 获取设置数据类型的列信息。CStoreRewriteColumn* setDataTypeColInfo = m_SDTColsInfo[sdtIndex];// 创建一个虚假的 TupleTableSlot。TupleTableSlot* fakeSlot = MakeSingleTupleTableSlot(m_OldTupDesc);// 获取要处理的属性索引。int attrIndex = setDataTypeColInfo->attrno - 1;Form_pg_attribute pColOldAttr = &m_OldTupDesc->attrs[attrIndex];Form_pg_attribute pColNewAttr = &m_NewTupDesc->attrs[attrIndex];// 限制运行时使用的内存大小。MemoryContext oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(m_estate));// 如果新值不应依赖于任何其他现有列或其现有值,则构造一个虚假的元组。bool shouldFree = false;Datum attOldVal = CStore::CudescTupGetMinMaxDatum(oldColCudesc, pColOldAttr, true, &shouldFree);HeapTuple fakeTuple = CreateFakeHeapTup(m_OldTupDesc, attrIndex, attOldVal);(void)ExecStoreTuple(fakeTuple, fakeSlot, InvalidBuffer, false);m_econtext->ecxt_scantuple = fakeSlot;// 计算新值,如果新值为空且列不允许为空,则报错。Assert(setDataTypeColInfo->newValue);bool attNewIsNull = false;Datum attNewValue = ExecEvalExpr(setDataTypeColInfo->newValue->exprstate, m_econtext, &attNewIsNull);if (attNewIsNull && setDataTypeColInfo->notNull) {ereport(ERROR,(errcode(ERRCODE_NOT_NULL_VIOLATION),errmsg("column \"%s\" contains null values", NameStr(pColOldAttr->attname)),errdetail("existing data violate the NOT NULL constraint.")));}// 根据是否需要更改表空间,调用 HandleCuWithSameValue 函数处理相同值的列单元。if (m_TblspcChanged) {HandleCuWithSameValue<true>(m_OldHeapRel,pColNewAttr,attNewValue,attNewIsNull,m_SDTColsMinMaxFunc[sdtIndex],m_SDTColsWriter[sdtIndex],newColCudesc,m_SDTColAppendOffset + sdtIndex);} else {HandleCuWithSameValue<false>(m_OldHeapRel,pColNewAttr,attNewValue,attNewIsNull,m_SDTColsMinMaxFunc[sdtIndex],m_SDTColsWriter[sdtIndex],newColCudesc,NULL);}// 如果需要释放内存,则释放。if (shouldFree) {pfree(DatumGetPointer(attOldVal));attOldVal = (Datum)0;}heap_freetuple(fakeTuple);fakeTuple = NULL;ExecDropSingleTupleTableSlot(fakeSlot);fakeSlot = NULL;(void)MemoryContextSwitchTo(oldCxt);
}

SetDataTypeHandleNormalCu 函数

  SetDataTypeHandleNormalCu 函数实现了将旧列数据类型转换为新列数据类型的功能。它首先加载旧列的数据,然后逐个转换为新的数据类型,并在此过程中考虑空值情况。接着,它将转换后的数据写入新的列文件中,并确保根据需要进行数据压缩,并将数据推送到复制队列,以便于数据的备份和同步。整体上,该代码负责将数据库中的旧列数据按照新的数据类型进行处理和迁移,以满足数据模式变更的需求。具体而言,它执行以下操作:

  1. 加载旧列的一个列单元(Column Unit,CU)数据,这是一组连续的行数据。
  2. 将旧列中的每个值转换为新的数据类型,同时考虑是否存在空值。
  3. 根据转换后的值和空值情况,形成新的列单元数据。
  4. 如果需要,将新的列单元数据压缩,并根据压缩模式设置相应的压缩方式。
  5. 将新的列单元数据写入新的列单元文件,其中可能会利用旧列单元文件中的空闲空间。
  6. 将新的列单元数据推送到复制队列,以便进行数据复制。

  函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.cpp

/** @Description: 处理普通的列单元,加载旧列的一个 CU,将每个值转换为新数据类型,然后写入新的 CU 文件。* @IN sdtIndex: 设置数据类型的列索引* @IN delMaskDataPtr: 删除掩码数据指针* @IN oldColCudesc: 旧列的 CU 描述* @OUT newColCudesc: 新列的 CU 描述*/
void CStoreRewriter::SetDataTypeHandleNormalCu(_in_ int sdtIndex, _in_ const char* delMaskDataPtr, _in_ CUDesc* oldColCudesc, _out_ CUDesc* newColCudesc)
{// 断言旧列的 CU 不是全空(NULL)的列单元。Assert(!oldColCudesc->IsNullCU());// 断言旧列的 CU 不是相同值(SAME VALUE)的列单元。Assert(!oldColCudesc->IsSameValCU());// 获取设置数据类型的列信息。CStoreRewriteColumn* setDataTypeColInfo = m_SDTColsInfo[sdtIndex];// 创建一个虚假的 TupleTableSlot。TupleTableSlot* fakeSlot = MakeSingleTupleTableSlot(m_OldTupDesc);// 获取要处理的属性索引。int attrIndex = setDataTypeColInfo->attrno - 1;Form_pg_attribute pColOldAttr = &m_OldTupDesc->attrs[attrIndex];Form_pg_attribute pColNewAttr = &m_NewTupDesc->attrs[attrIndex];/* 其他字段稍后设置。 */int rowsCntInCu = oldColCudesc->row_count;Assert(rowsCntInCu <= RelMaxFullCuSize);newColCudesc->cu_id = oldColCudesc->cu_id;newColCudesc->row_count = oldColCudesc->row_count;newColCudesc->magic = GetCurrentTransactionIdIfAny();/* 加载旧 CU 的所有值。 */CU* oldCu = LoadSingleCu::LoadSingleCuData(oldColCudesc,attrIndex,pColOldAttr->attlen,pColOldAttr->atttypmod,pColOldAttr->atttypid,m_OldHeapRel,m_SDTColsReader[sdtIndex]);// 声明一个指针数组,用于存储获取值的函数指针GetValFunc getValFuncPtr[1];// 初始化获取值的函数指针数组InitGetValFunc(pColOldAttr->attlen, getValFuncPtr, 0);// 根据旧列单元是否含有空值来确定获取值的函数指针的索引int oldCuGetValFuncId = oldCu->HasNullValue() ? 1 : 0;// 标志是否为第一次处理值bool firstFlag = true;// 标志旧列单元是否全为空值bool fullNull = true;// 标志新列单元是否含有空值bool hasNull = false;// 记录可变长度字符串的最大长度int maxVarStrLen = 0;/* 切换内存上下文。 */MemoryContext oldCxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(m_estate));for (int cnt = 0; cnt < rowsCntInCu; ++cnt) {if ((delMaskDataPtr && IsBitmapSet((unsigned char*)delMaskDataPtr, (uint32)cnt)) || oldCu->IsNull(cnt)) {/** case 1: 旧值已删除,或* case 2: 旧值为空值。* 因此跳过旧数据,并在新 CU 中设置为 NULL。*/m_SDTColValues[cnt] = (Datum)0;m_SDTColIsNull[cnt] = true;hasNull = true;continue;}/** 因为 getValFuncPtr[0]() 不处理 NULL 值,所以* 我们必须先考虑 NULL 值。*/Datum attOldVal = getValFuncPtr[0][oldCuGetValFuncId](oldCu, cnt);HeapTuple fakeTuple = CreateFakeHeapTup(m_OldTupDesc, attrIndex, attOldVal);(void)ExecStoreTuple(fakeTuple, fakeSlot, InvalidBuffer, false);m_econtext->ecxt_scantuple = fakeSlot;m_SDTColIsNull[cnt] = false;m_SDTColValues[cnt] =ExecEvalExpr(setDataTypeColInfo->newValue->exprstate, m_econtext, (m_SDTColIsNull + cnt));if (!m_SDTColIsNull[cnt]) {fullNull = false;if (m_SDTColsMinMaxFunc[sdtIndex]) {/* 计算此列所有新值的最小/最大值。 */m_SDTColsMinMaxFunc[sdtIndex](m_SDTColValues[cnt], newColCudesc, &firstFlag);/* 如果新数据类型没有可变长度,则不关心 *maxVarStrLen*。 */if (pColNewAttr->attlen < 0) {maxVarStrLen = Max(maxVarStrLen,(int)datumGetSize(m_SDTColValues[cnt], pColNewAttr->attbyval, pColNewAttr->attlen));}}} else {if (setDataTypeColInfo->notNull) {ereport(ERROR,(errcode(ERRCODE_NOT_NULL_VIOLATION),errmsg("column \"%s\" contains null values", NameStr(pColOldAttr->attname)),errdetail("existing data violate the NOT NULL constraint.")));}m_SDTColValues[cnt] = (Datum)0;hasNull = true;}/** 注意:绝对不要释放 *fakeTuple*,因为 *m_SDTColValues[]** 可能正在使用其值和内存空间。*/}ExecDropSingleTupleTableSlot(fakeSlot);fakeSlot = NULL;(void)MemoryContextSwitchTo(oldCxt);/** 在调用 *InitMem()* 时,我们必须指示新的 CU 是否具有 NULL 值。* 这取决于三种情况:1)旧 CU 具有空值;2)旧 CU 的某些值已删除;3)新值是空值。*/CU* newCu = New(CurrentMemoryContext) CU(pColNewAttr->attlen, pColNewAttr->atttypmod, pColNewAttr->atttypid);newCu->InitMem(sizeof(Datum) * rowsCntInCu, rowsCntInCu, hasNull);/* 形成新的 CU 数据 */if (!hasNull) {/* 尽量减少 IF 跳转次数。 */for (int cnt = 0; cnt < rowsCntInCu; ++cnt) {CU::AppendCuData(m_SDTColValues[cnt], 1, pColNewAttr, newCu);}} else {for (int cnt = 0; cnt < rowsCntInCu; ++cnt) {if (!m_SDTColIsNull[cnt]) {CU::AppendCuData(m_SDTColValues[cnt], 1, pColNewAttr, newCu);} else {newCu->AppendNullValue(cnt);}}}/* 设置 cudesc 模式并根据需要将数据写入 CU 文件。 */if (CStore::SetCudescModeForMinMaxVal(fullNull,m_SDTColsMinMaxFunc[sdtIndex] != NULL,hasNull,maxVarStrLen,pColNewAttr->attlen,newColCudesc)) {int16 compressing_modes = 0;/* 设置压缩模式 */heaprel_set_compressing_modes(m_OldHeapRel, &compressing_modes);CompressCuData(newCu, newColCudesc, pColNewAttr, compressing_modes);if (m_TblspcChanged) {/* 直接获取写入偏移量 */newColCudesc->cu_pointer = m_SDTColAppendOffset[sdtIndex];/* 更新 *m_SDTColAppendOffset* 以便下一次写入。 */m_SDTColAppendOffset[sdtIndex] += newColCudesc->cu_size;} else {/** 首先尝试在旧 CU 文件中使用空闲空间。* 在最坏的情况下,直接追加新数据到旧 CU 文件。*/newColCudesc->cu_pointer = m_SDTColsWriter[sdtIndex]->AllocSpace(newColCudesc->cu_size);}SaveCuData(newCu, newColCudesc, m_SDTColsWriter[sdtIndex]);/* 将 CU 数据推送到复制队列 */CStoreCUReplication((this->m_TblspcChanged ? this->m_CUReplicationRel : m_OldHeapRel),setDataTypeColInfo->attrno,newCu->m_compressedBuf,newColCudesc->cu_size,newColCudesc->cu_pointer);}DELETE_EX(oldCu);DELETE_EX(newCu);
}

FetchCudescFrozenXid 函数

  FetchCudescFrozenXid 函数的主要功能是根据旧的 CU 描述堆(heap)计算出用于冻结和清除死元组的事务 ID。它首先确定了最老的活动事务IDOldestXmin),然后根据该信息设置了新的 CU 描述冻结的事务IDm_NewCudescFrozenXid)。随后,它通过检查系统表 pg_class 中的 relfrozenxid 字段来获取旧的 CU 描述堆的冻结事务ID,并与当前的 ShmemVariableCache 中的 nextXid 进行比较,确保冻结事务 ID 不会后退。最后,如果新的 CU 描述冻结的事务 ID 早于旧的 CU 描述冻结的事务 ID,则使用旧的 CU 描述冻结的事务 ID。函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.cpp

/* description: 未来计划 - 检查 pFreezeXid 的提取方法,参考 copy_heap_data()。*/
void CStoreRewriter::FetchCudescFrozenXid(Relation oldCudescHeap)
{// 计算用于冻结和清除死元组的事务ID。我们使用 -1 作为 freeze_min_age 来避免比普通 VACUUM 更早地冻结元组。//Assert(oldCudescHeap->rd_rel->relisshared == false);TransactionId OldestXmin = InvalidTransactionId;vacuum_set_xid_limits(oldCudescHeap, -1, -1, &OldestXmin, &m_NewCudescFrozenXid, NULL, NULL);// FreezeXid 将成为表的新 relfrozenxid,并且不能后退,因此取最大值。//bool isNull = false;TransactionId oldFrozenXid;Relation rel = heap_open(RelationRelationId, AccessShareLock);HeapTuple tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(RelationGetRelid(oldCudescHeap)));if (!HeapTupleIsValid(tuple)) {ereport(ERROR,(errcode(ERRCODE_CACHE_LOOKUP_FAILED),errmsg("cache lookup failed for relation %u", RelationGetRelid(oldCudescHeap))));}Datum xid64datum = heap_getattr(tuple, Anum_pg_class_relfrozenxid64, RelationGetDescr(rel), &isNull);heap_close(rel, AccessShareLock);heap_freetuple(tuple);tuple = NULL;if (isNull) {oldFrozenXid = oldCudescHeap->rd_rel->relfrozenxid;if (TransactionIdPrecedes(t_thrd.xact_cxt.ShmemVariableCache->nextXid, oldFrozenXid))oldFrozenXid = FirstNormalTransactionId;} elseoldFrozenXid = DatumGetTransactionId(xid64datum);// 如果新的 CU 描述冻结的事务ID早于旧的 CU 描述冻结的事务ID,则使用旧的 CU 描述冻结的事务ID。//if (TransactionIdPrecedes(m_NewCudescFrozenXid, oldFrozenXid)) {m_NewCudescFrozenXid = oldFrozenXid;}
}

InsertNewCudescTup 函数

  InsertNewCudescTup 函数的作用是向 CUDesc 表中插入新的元组。首先,它准备了用于存储 CUDesc 元组值和是否为 NULL 的数组。然后,它进入批量内存上下文以准备进行批量插入操作。接着,它调用 CStore::FormCudescTuple 函数创建一个 CUDesc 元组,并将其插入到批量插入器中。最后,它离开批量内存上下文,完成插入操作。函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.cpp

void CStoreRewriter::InsertNewCudescTup(_in_ CUDesc* pCudesc, _in_ TupleDesc pCudescTupDesc, _in_ Form_pg_attribute pColNewAttr)
{// 准备存储 CUDesc 元组的值和是否为 NULL 的数组Datum values[CUDescMaxAttrNum];bool isnull[CUDescMaxAttrNum];// 进入批量内存上下文以进行批量插入操作m_NewCudescBulkInsert->EnterBulkMemCnxt();// 创建 CUDesc 元组并插入到批量插入器中HeapTuple tup = CStore::FormCudescTuple(pCudesc, pCudescTupDesc, values, isnull, pColNewAttr);m_NewCudescBulkInsert->BulkInsert(tup);// 离开批量内存上下文m_NewCudescBulkInsert->LeaveBulkMemCnxt();
}

HandleWholeDeletedCu 函数

  HandleWholeDeletedCu 函数的作用是处理整个被删除的压缩单元(CU。它首先创建一个完全为 NULLCUDesc 对象,并填充了该压缩单元的 ID、行数以及标记为 NULL。然后,它遍历每个需要重写的列的信息,并将完全为 NULLCUDesc 信息插入到新的 CUDesc 表中,以记录这些列在此压缩单元中的删除操作。函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.cpp

void CStoreRewriter::HandleWholeDeletedCu(_in_ uint32 cuId, _in_ int rowsCntInCu, _in_ int nRewriteCols, _in_ CStoreRewriteColumn** rewriteColsInfo)
{// 创建一个完全为 NULL 的 CUDesc 对象,并填充 cuId、行数以及标记为 NULLCUDesc fullNullCudesc;fullNullCudesc.cu_id = cuId;fullNullCudesc.row_count = rowsCntInCu;fullNullCudesc.SetNullCU();fullNullCudesc.magic = GetCurrentTransactionIdIfAny();// 遍历每个重写列的信息for (int i = 0; i < nRewriteCols; ++i) {// 向新的 CUDesc 表中插入新的 CUDesc 元组,将完全为 NULL 的 CUDesc 信息插入其中InsertNewCudescTup(&fullNullCudesc, RelationGetDescr(m_NewCudescRel), &m_NewTupDesc->attrs[(rewriteColsInfo[i]->attrno - 1)]);}
}

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

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

相关文章

第十二届蓝桥杯省赛真题 Java B 组【原卷】

文章目录 发现宝藏【考生须知】试题 A: ASC试题 B : 卡片试题 C: 直线试题 D: 货物摆放试题 E: 路径试题 F: 时间显示试题 G: 最少砝码试题 H: 杨辉三角形试题 I: 双向排序试题 J: 括号序列 发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;…

echarts指标盘属性概括

echarts指标盘属性概括 代码 有模拟数据可以直接使用const options {animation: true,title: {top: "35%",left: "center",// text: "单元测试覆盖度", // 主标题itemGap: 15,textStyle: {// 主标题样式color: "#666666",fontSize:…

YOLOv5改进 | 独家创新篇 | 利用MobileNetV4的UIB模块二次创新C3(全网独家首发)

一、本文介绍 本文给大家带来的改进机制是利用MobileNetV4的UIB模块二次创新C3&#xff0c;其中UIB模块来自2024.5月发布的MobileNetV4网络&#xff0c;其是一种高度优化的神经网络架构&#xff0c;专为移动设备设计。它最新的改动总结主要有两点&#xff0c;采用了通用反向瓶…

微同城小程序源码 轻松制作本地生活活动赚钱 带完整的安装代码包以及搭建教程

近年来&#xff0c;本地生活服务市场蓬勃发展&#xff0c;人们对于周边的生活信息、活动资讯等需求日益增长。然而&#xff0c;传统的信息发布方式存在诸多不便&#xff0c;如信息更新不及时、传播范围有限等。微同城小程序源码应运而生。它利用小程序的便捷性和普及性&#xf…

海睿思受邀参加 “走进中节能”研习交流,探索新能源数据治理的创新路径

近日&#xff0c;OceanMind海睿思参加由江苏省企业信息化协会&#xff08;以下简称“苏信会”&#xff09;主办的“走进中节能太阳能科技&#xff08;镇江&#xff09;有限公司”研习交流活动。 海睿思与苏美达、远东控股、隆基乐叶、固德威、上能电气等40多位来自制造业领域的…

04-25 周四 FastBuild重构实践-TLS、全局捕获异常、一键配置

04-25 周四 FastBuild重构实践 时间版本修改人描述04-25V0.1宋全恒新建文档2024年5月6日14:33:16V1.0宋全恒完成文档撰写 简介 由于 04-22 周日 阿里云-瑶光上部署FastBuild过程(配置TLS、自定义辅助命令)描述了重新部署一个FastBuild实例的过程&#xff0c;通过阅读这个&…

怎么设置付费视频课程_在线教育知识付费系统

在信息爆炸的时代&#xff0c;我们每天都被海量的信息包围。然而&#xff0c;真正有价值、能够让我们快速提升的知识&#xff0c;往往隐藏在这些信息的深海之中。今天&#xff0c;我要为大家介绍的&#xff0c;就是这样一份珍贵的宝藏——我们的付费视频课程。 工具/原料 微信…

手把手教你微调Stable Diffusion

温馨提示 关于本文&#xff1a; 本文你可以学习到完整的不使用webui借助lora和dreambooth微调Stable Diffusion的全过程。 手把手教你微调Stable Diffusion生成优弧&#xff0c;但是半失败版&#x1f602; 关于训练&#xff1a; 单卡32GV100进行的微调&#xff0c;因为一些…

【leetcode】数学位数题总结

涉及题型&#xff1a;两数相加问题、大数溢出等 相加问题 根据题意定义rs的数据结构判断是存储方式是正序还是逆序&#xff0c;如果是正序需要反转 比如 123 12 135是正序&#xff0c; 321 21 135是逆序反转的方式&#xff1a;对于可以从后往前遍历的&#xff08;如字符串…

高效工作之软件系统——数据结构登记表

数据结构模板 开发完软件系统后&#xff0c;往往需要进行一些登记——《软件系统数据结构登记表》 然后软件项目有60个表左右&#xff0c;难道需要手动录入&#xff0c;那肯定不可能 工欲善其事必先利其器&#xff01;go。。。同事给的模板是下图 效果图 于是想到 之前使用…

【mysql】mysql导入导出数据详解

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

初中都没念完的我,是怎么从IT这行坚持下去的...

大家好&#xff0c;我是一名二线&#xff08;伪三线&#xff0c;毕竟连续两年二线城市了&#xff09;的程序员。 现阶段状态在职&#xff0c;28岁&#xff0c;工作了10年左右&#xff0c;码农从事了5年左右&#xff0c;现薪资9k左右。如文章标题所说&#xff0c;初二辍学&…

AVL树的原理及其实现

文章目录 前言了解AVL树AVL树的特点AVL树的节点调整方案右单旋为什么要右单旋呢&#xff1f;右单旋代码 左单旋为什么要左单旋&#xff1f;左单旋代码 左右双旋左右双旋之后平衡因子的情况左右双旋代码实现 右左双旋右左双旋代码&#xff1a; 简单测试 前言 回顾我们对于二叉搜…

Altman确认:神秘Chatbot非GPT-4.5,OpenAI搜索引擎即将上线

&#x1f680; Altman确认&#xff1a;神秘Chatbot非GPT-4.5&#xff0c;OpenAI搜索引擎即将上线 摘要&#xff1a;近日&#xff0c;Sam Altman在哈佛大学的演讲中确认&#xff0c;引发广泛猜测的gpt2-chatbot并非OpenAI即将发布的下一代模型GPT-4.5。与此同时&#xff0c;关于…

亚信安慧AntDB:解锁数智化的新时代

亚信安慧AntDB的融合实时的特性使得它在数据库领域独树一帜。传统的数据库系统往往只能追求数据的准确性和一致性&#xff0c;但在实际的业务场景中&#xff0c;这些特性并不能满足企业的需求。AntDB的出现打破了传统束缚&#xff0c;为企业带来了全新的数据处理方式&#xff0…

低代码审计作业平台:引领企业实现审计高效革命

随着信息化时代的深入发展&#xff0c;审计工作面临着前所未有的挑战与机遇。传统的审计方式往往繁琐复杂&#xff0c;效率低下&#xff0c;已无法满足现代企业对高效、准确、智能的审计需求。在这样的背景下&#xff0c;审计作业低代码平台应运而生&#xff0c;以其独特的优势…

B/S模式的web通信(高并发服务器)

这里写目录标题 目标实现的目标 服务器代码&#xff08;采用epoll实现服务器&#xff09;整体框架main函数init_listen_fd函数&#xff08;负责对lfd初始化的那一系列操作&#xff09;epoll_run函数do_accept函数do_read函数内容补充&#xff1a;http中的getline函数 详解do_re…

【C++初阶】第十站:vector 中通用函数的模拟实现

目录 vector中的三个重要迭代器 默认成员函数 构造函数(无参构造) 构造函数(函数模板) 构造函数(带有默认参数) size_t int 拷贝构造函数 赋值重载 析构函数 迭代器相关函数 begin和end 容量和大小相关函数 size capacity resize 修改容器内容相关函数 reser…

不想让Win系统更新,那就让它暂停一万年

按照下图所示进行操作 winR 输入 regedit&#xff0c;进入注册表编辑器 随后依次点击 HKEY_LOCAL_MACHINE ⬇ SOFTWARE ⬇ Microsoft ⬇ WindowsUpdate ⬇ UX ⬇ Settings 最后在右侧空白处 文件类型 新建DWORD&#xff08;32位&#xff09;值&#xff08;D&#xff09; 命名…

PyQt5的布局管理

文章目录 1.垂直布局和水平布局垂直布局&#xff08;QVBoxLayout&#xff09;&#xff1a;水平布局&#xff08;QHBoxLayout&#xff09;&#xff1a; 2. 布局中的addStrech2.1 我们首先看只有一个Strech的情况&#xff0c;比较容易理解2.2 两个Strech2.3 多个Strech 3.栅格布局…