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

列存储(analyze)

  • AcquireSampleCStoreRows 函数
    • es_get_attnums_to_analyze 函数
    • CStoreRelGetCUNumByNow 函数
    • CStore::GetLivedRowNumbers 函数
    • InitGetValFunc 函数
    • CStoreGetfstColIdx 函数
    • CStore::GetCUDesc 函数
    • CStore::IsTheWholeCuDeleted 函数
    • CStore::IsTheWholeCuDeleted 函数
    • CStore::CudescTupGetMinMaxDatum 函数

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

AcquireSampleCStoreRows 函数

  在前一章中(列存储(analyze)(三)),我们对函数 acquire_sample_rows 进行了学习,其中acquire_sample_rows 函数的作用是在 PostgreSQL 数据库中执行抽样操作,用于获取数据表的随机样本行。这个函数的主要目的是估计数据表的统计信息和数据分布,以帮助查询优化器生成更有效的执行计划
  本章,我们紧接着来看另一种数据库抽样操作 AcquireSampleCStoreRows ,该函数也是我们所需关注的重点,是本【OpenGauss源码学习 —— 列存储(analyze)】系列的重点和最终学习目标。AcquireSampleCStoreRows 是用于列式存储引擎(如列存储数据库表)的函数。它是为了从列存储表中获取样本数据而设计的,通常用于分析和统计查询中,以获取表的随机样本,以便估计表的统计信息和数据分布。
  其中,函数 AcquireSampleCStoreRows 的调用关系与 acquire_sample_rows 相同,如下所示:do_analyze_rel -> get_target_rows -> AcquireSampleCStoreRows

  函数的入参含义如下:

  1. Relation onerel: 被分析的关系(表)的 Relation 结构,表示要进行样本抽样的目标表。调试信息如下:
    在这里插入图片描述
  2. int elevel: 日志消息的错误级别。用于确定错误消息的日志级别。
  3. HeapTuple* rows: 一个用于存储样本数据的数组。函数将抽样的行数据存储在这个数组中。
  4. int64 targrows: 目标抽样行数,即希望从表中抽取的行数。
  5. double* totalrows: 用于存储表的估计总行数的指针。函数将估计的总行数存储在这个指针指向的变量中。
  6. double* totaldeadrows: 用于存储表的估计死行数的指针。函数将估计的死行数存储在这个指针指向的变量中。
  7. VacAttrStats** vacattrstats: 一个 VacAttrStats 结构的数组,表示用于分析的每个属性的统计信息。这些统计信息包括样本数据的属性分布等。
    在这里插入图片描述
  8. int analyzeAttrNum: 要分析的属性的数量。表示要分析的属性数量。

  AcquireSampleCStoreRows 函数的作用和目的是执行列存储表格的抽样操作,以获取列存储表格的随机样本行AcquireSampleCStoreRows 函数源码如下:(路径:src/gausskernel/optimizer/commands/analyze.cpp

** AcquireSampleCStoreRows -- acquire a random sample of rows from the table** Selected rows are returned in the caller-allocated array rows[], which* must have at least targrows entries.* The actual number of rows selected is returned as the function result.* We also estimate the total number of live and dead rows in the table,* and return them into *totalrows and *totaldeadrows, respectively.** To improve the analyze efficiency in the condition of severl attrs are selected,* we add two parameters which record the information of seleced attrs.The perform* can only used for column storage table.** The returned list of tuples is in order by physical position in the table.* (We will rely on this later to derive correlation estimates.)*/
template <bool estimate_table_rownum>
static int64 AcquireSampleCStoreRows(Relation onerel, int elevel, HeapTuple* rows, int64 targrows, double* totalrows,double* totaldeadrows, VacAttrStats** vacattrstats, int analyzeAttrNum)
{int64 numrows = 0;             /* 当前在样本中的行数 */int64 delta_numrows = 0;       /* 当前在样本中的差异表行数 */int32 samplerows = 0;          /* 收集的总行数 */int64 liverows = 0;            /* 观察到的存活行数 */int64 deadrows = 0;            /* 观察到的已删除行数 */BlockNumber totalblocks = 0;   /* 总块数 */BlockNumber estBlkNum = 0;     /* 估算的块数 */BlockSamplerData bs;           /* 块采样数据结构 */double rstate;                 /* 随机状态 */AnlPrefetch anlprefetch;       /* 预取设置 */int cusize_threshold = maxAnalyzeUseCstoreBufferSize; /* CU大小阈值 */anlprefetch.blocklist = NULL;  /* 预取块列表初始化为空 */AssertEreport(targrows > 0, MOD_OPT, "采样时目标行数必须大于0");/** 所有系统表都是行存储表,列存储表必须是用户定义的表,* 因此我们只返回而不采样。*/if (IS_PGXC_COORDINATOR) {*totalrows = 0;*totaldeadrows = 0;return 0;}/* 为扩展的统计信息准备 */Bitmapset* bms_attnums = es_get_attnums_to_analyze(vacattrstats, analyzeAttrNum);int num_attnums = bms_num_members(bms_attnums);   /* 统计属性的数量 */int colTotalNum = onerel->rd_att->natts;          /* 表中总的列数 */int32* attrSeq = NULL;                           /* 属性顺序 */int16* colIdx = (int16*)palloc0(sizeof(int16) * num_attnums); /* 列索引数组 */int* slotIdList = NULL;                          /* CU槽位索引数组 */CStoreScanDesc cstoreScanDesc = NULL;            /* 列存储扫描描述符 */CU** cuPtr = NULL;                               /* CU指针数组 */Relation deltarel = NULL;                        /* 差异表 */double totalRowCnts = 0;                         /* 总行数 */double deltarows = 0;                            /* 差异表的行数 */double curows = 0;                               /* CU中的行数 */double cudeadrows = 0;                           /* CU中的已删除行数 *//* 首先考虑差异关系(delta relation) */Assert(OidIsValid(onerel->rd_rel->reldeltarelid));/* 打开差异表。我们只需要对其进行 AccessShareLock。 */deltarel = relation_open((onerel->rd_rel->reldeltarelid), AccessShareLock);/* 从差异关系中仅获取总行数的估计 */delta_numrows = acquire_sample_rows<true>(deltarel, elevel, rows, targrows, &deltarows, totaldeadrows);/* 在这里将 nulls 设置为 true,并且那些不需要分析的属性将始终为 null */for (int i = 0; i < num_attnums; ++i) {colIdx[i] = bms_first_member(bms_attnums);}/* 为当前分析初始化本地快照(local snapshot)。 */Snapshot tmpSnapshot = GetActiveSnapshot();Assert(tmpSnapshot != NULL);cstoreScanDesc = CStoreBeginScan(onerel, num_attnums, colIdx, tmpSnapshot, false);// 开始对列存储表进行扫描,准备抽样// onerel: 当前关系// num_attnums: 要分析的属性数量// colIdx: 要分析的属性列的索引// tmpSnapshot: 当前快照// false: 是否是 Delta 表的扫描totalblocks = CStoreRelGetCUNumByNow(cstoreScanDesc);// 获取当前 CU 的数量,用于统计curows = (double)cstoreScanDesc->m_CStore->GetLivedRowNumbers(&deadrows);// 从 CU 中获取存活行数,并更新 deadrows// curows: CU 中存活的行数// deadrows: 已删除的行数if (curows <= 0.0) {// 如果存活行数小于等于0,则没有需要分析的数据relation_close(deltarel, AccessShareLock); // 关闭 Delta 表CStoreEndScan(cstoreScanDesc); // 结束对列存储表的扫描pfree_ext(colIdx); // 释放 colIdx 的内存pfree_ext(bms_attnums); // 释放 bms_attnums 的内存return 0; // 返回0,表示没有进行抽样}*totalrows = totalRowCnts = curows + deltarows;// 设置总行数为存活行数加上 Delta 表的行数// totalrows: 总行数// totalRowCnts: 存活行数加上 Delta 表的行数cudeadrows = (double)deadrows;*totaldeadrows += cudeadrows;// 更新总的已删除行数deadrows = 0; // 在非 pretty 模式下重用 deadrows 来存储已删除元组的数量/* for pretty mode we just return actual lived rows */if (estimate_table_rownum && SUPPORT_PRETTY_ANALYZE) {// 如果是 "pretty" 模式并且需要估算表的行数// "pretty" 模式通常用于漂亮的输出,返回实际存活的行数,而不进行估算/* only get estimate rows, free delta rows and close relation */// 只获取估算的行数,释放 Delta 表的行并关闭关系for (int i = 0; i < delta_numrows; i++) {if (rows[i]) {pfree_ext(rows[i]); // 释放行的内存rows[i] = NULL;}}relation_close(deltarel, AccessShareLock); // 关闭 Delta 表CStoreEndScan(cstoreScanDesc); // 结束对列存储表的扫描pfree_ext(colIdx); // 释放 colIdx 的内存pfree_ext(bms_attnums); // 释放 bms_attnums 的内存return 0; // 返回0,表示没有进行抽样}attrSeq = (int*)palloc0(sizeof(int32) * num_attnums);// 分配内存以存储属性的顺序slotIdList = (int*)palloc0(sizeof(int) * num_attnums);// 分配内存以存储槽位的列表cuPtr = (CU**)palloc0(sizeof(CU*) * colTotalNum);// 分配内存以存储 CU 指针的数组if (!estimate_table_rownum) {// 如果不需要估算表的行数(非 pretty 模式)/* free rows */// 释放之前分配的行内存for (int i = 0; i < delta_numrows; i++) {if (rows[i]) {pfree_ext(rows[i]);rows[i] = NULL;}}int64 deltatargrows = 0;// 初始化 Delta 表的目标行数为0/* recalculate target num */// 重新计算目标行数,以根据 Delta 表的情况进行调整if (deltarows > 0) {// 如果 Delta 表中有存活的行deltatargrows = (int)rint(targrows * deltarows / totalRowCnts);// 根据存活行数比例重新计算目标行数/* Make sure we don't overrun due to roundoff error */// 确保不因四舍五入误差而超出目标行数deltatargrows = Min(deltatargrows, targrows);// 限制目标行数不超过原始目标行数if (deltatargrows > 0) {double trows = 0;double tdrows = 0;/* Fetch a random sample of the delta table's rows */// 从 Delta 表中获取随机样本行delta_numrows = acquire_sample_rows<false>(deltarel, elevel, rows, deltatargrows, &trows, &tdrows);// 重新抽样 Delta 表/* fix total rows */// 更新总行数和已删除行数*totalrows = trows + curows;*totaldeadrows = tdrows + cudeadrows;}}/* change cstore target num */// 调整列存储表的目标行数int64 temp_target = (int)rint(targrows * curows / totalRowCnts);/* Make sure we don't overrun due to roundoff error */// 确保不因四舍五入误差而超出目标行数temp_target = Min(temp_target, targrows - deltatargrows);targrows = temp_target;/** calculate cu number by following formula* 1. get lived row number* 2. estimate row size by suppose the width of each column is 4* 3. estimate how many pages needed if it's row-stored* 4. evaluate how many CUs needed to sample*/// 通过以下公式计算 CU 数:// 1. 获取存活行数// 2. 假定每列的宽度为4,估算行大小// 3. 估算如果是按行存储需要多少页// 4. 评估需要采样的 CU 数int totalwidth = 0;int relpages = 0;BlockNumber sampleCUs = 0;totalwidth = 4 * onerel->rd_att->natts;relpages = ceil(*totalrows * totalwidth / BLCKSZ);if (relpages == 0) {relpages = 1;}sampleCUs = ceil((double)targrows / relpages * totalblocks);sampleCUs = (sampleCUs > totalblocks) ? totalblocks : sampleCUs;elog(DEBUG2, "ANALYZE INFO : sample %u cu from totoal %u cu", sampleCUs, totalblocks);// 输出采样的 CU 数和总 CU 数BlockSampler_Init(&bs, totalblocks, sampleCUs);} else {AssertEreport(!SUPPORT_PRETTY_ANALYZE, MOD_OPT, "");// 如果需要估算表的行数,并且不支持 "pretty" 模式分析,则直接采样指定数量的行BlockSampler_Init(&bs, totalblocks, targrows);elog(DEBUG2, "ANALYZE INFO : sample %ld rows from totoal %u cu", targrows, totalblocks);}rstate = anl_init_selection_state(targrows);// 初始化行采样状态/* Prepare for sampling rows */// 为行采样做准备CStore* cstore = cstoreScanDesc->m_CStore;Form_pg_attribute* attrs = cstore->m_relation->rd_att->attrs;GetValFunc* getValFuncPtr = (GetValFunc*)palloc(sizeof(GetValFunc) * colTotalNum);/* change sample rows pointer */// 移动指针以指向下一个要采样的行rows += delta_numrows;for (int col = 0; col < num_attnums; ++col) {int colSeq = attrSeq[col] = colIdx[col] - 1;// 获取列的顺序,并将其保存在 attrSeq 数组中InitGetValFunc(attrs[colSeq]->attlen, getValFuncPtr, colSeq);// 初始化获取列值的函数指针}ADIO_RUN(){uint32 quantity = (uint32)CSTORE_ANALYZE_PREFETCH_QUANTITY;// 获取预取数量anlprefetch.fetchlist1.size = (uint32)((quantity > (totalblocks / 2 + 1)) ? (totalblocks / 2 + 1) : quantity);// 设置第一个预取列表的大小,限制在总块数的一半加一以内anlprefetch.fetchlist1.blocklist = (BlockNumber*)palloc(sizeof(BlockNumber) * anlprefetch.fetchlist1.size);// 为第一个预取列表分配内存anlprefetch.fetchlist1.anl_idx = 0;anlprefetch.fetchlist1.load_count = 0;anlprefetch.fetchlist2.size = anlprefetch.fetchlist1.size;// 第二个预取列表与第一个相同大小anlprefetch.fetchlist2.blocklist = (BlockNumber*)palloc(sizeof(BlockNumber) * anlprefetch.fetchlist2.size);// 为第二个预取列表分配内存anlprefetch.fetchlist2.anl_idx = 0;anlprefetch.fetchlist2.load_count = 0;anlprefetch.init = false;// 初始化预取状态为未完成ereport(DEBUG1,(errmodule(MOD_ADIO),errmsg("analyze prefetch for %s prefetch quantity(%u)",RelationGetRelationName(onerel),anlprefetch.fetchlist1.size)));// 发送调试信息,表示进行分析预取,显示表名和预取数量}ADIO_END();MemoryContext sample_context; // 用于存储抽样数据的内存上下文MemoryContext old_context;    // 保存当前内存上下文,以便稍后恢复BlockNumber targblock;        // 目标块号int fstColIdx = CStoreGetfstColIdx(onerel); // 获取列存储表的第一列索引Datum* constValues = (Datum*)palloc(sizeof(Datum) * colTotalNum); // 存储常量值的数组bool* nullValues = (bool*)palloc(sizeof(bool) * colTotalNum);    // 存储空值信息的数组Datum* values = (Datum*)palloc0(sizeof(Datum) * colTotalNum);    // 存储数据值的数组,初始化为零bool* nulls = (bool*)palloc0(sizeof(bool) * colTotalNum);        // 存储是否为 null 值的数组,初始化为 falseint* funcIdx = (int*)palloc0(sizeof(int) * colTotalNum);         // 存储函数索引的数组,初始化为零List* sampleTupleInfo = NIL;    // 存储抽样元组信息的列表sample_tuple_cell* st_cell = NULL; // 抽样元组单元格ListCell* cell1 = NULL;       // 用于遍历列表的 ListCell 指针ListCell* cell2 = NULL;       // 用于遍历列表的 ListCell 指针ListCell* cell3 = NULL;       // 用于遍历列表的 ListCell 指针// 创建一个新的内存上下文,用于存储抽样数据,设置上下文名称和大小参数sample_context = AllocSetContextCreate(CurrentMemoryContext,"sample cstore rows for analyze",ALLOCSET_DEFAULT_MINSIZE,ALLOCSET_DEFAULT_INITSIZE,ALLOCSET_DEFAULT_MAXSIZE);while (InvalidBlockNumber != (targblock = BlockSampler_GetBlock<true>(cstoreScanDesc, &bs, &anlprefetch, num_attnums, attrSeq, estimate_table_rownum))) {List* valueLocation = NIL;     // 存储值的位置信息的列表List* targoffsetList = NIL;    // 存储目标偏移量的列表int location = 0;             // 当前位置CUDesc cuDesc;                // 列存储单元描述uint16 targoffset = 0;        // 目标偏移量uint16 maxoffset;             // 最大偏移量int64 total_cusize = 0;       // 总共的列存储单元大小int start_col = -1;           // 起始列索引int end_col = -1;             // 结束列索引bool first_batch = true;      // 是否是第一批次bool all_in_buffer = true;    // 是否所有数据都在缓冲区内targblock = targblock + FirstCUID + 1; // 计算目标块号// 如果获取列存储单元描述失败,则继续下一次循环if (cstoreScanDesc->m_CStore->GetCUDesc(fstColIdx, targblock, &cuDesc, tmpSnapshot) != true)continue;// 如果启用工作负载控制,进行 IO 调度和更新if (ENABLE_WORKLOAD_CONTROL)IOSchedulerAndUpdate(IO_TYPE_READ, 1, IO_TYPE_COLUMN);cstore->GetCUDeleteMaskIfNeed(targblock, tmpSnapshot); // 获取列存储单元删除掩码// 如果该列存储单元内的所有元组都已删除,则继续下一次循环if (cstore->IsTheWholeCuDeleted(cuDesc.row_count))continue;maxoffset = cuDesc.row_count;if (estimate_table_rownum) {liverows = maxoffset;estBlkNum++;if (liverows > 0)break;ereport(DEBUG1,(errmsg("ANALYZE INFO : estimate total rows of \"%s\" - no lived rows in cuid: %u",RelationGetRelationName(onerel),targblock)));totalblocks--;continue;}/* 重置 null 标志和常量值标志以供下一个块使用 */errno_t rc;// 将 nulls 数组的所有元素设置为 truerc = memset_s(nulls, sizeof(bool) * colTotalNum, true, sizeof(bool) * colTotalNum);securec_check(rc, "\0", "\0");// 将 nullValues 数组的所有元素设置为 falserc = memset_s(nullValues, sizeof(bool) * colTotalNum, false, sizeof(bool) * colTotalNum);securec_check(rc, "\0", "\0");// 将 constValues 数组的所有元素设置为 0rc = memset_s(constValues, sizeof(Datum) * colTotalNum, 0, sizeof(Datum) * colTotalNum);securec_check(rc, "\0", "\0");/* copy data from cu buffer */
#define copy_sample_dataum(dest, col_num, offset)                                   \do {                                                                            \old_context = MemoryContextSwitchTo(sample_context);                        \Datum dm = getValFuncPtr[colNum][funcIdx[colNum]](cuPtr[colNum], (offset)); \int16 valueTyplen = attrs[(col_num)]->attlen;                               \bool valueTypbyval = attrs[(col_num)]->attlen == 0 ? false : true;          \if (valueTyplen < 0)                                                        \(dest) = PointerGetDatum(PG_DETOAST_DATUM_COPY(dm));                    \else                                                                        \(dest) = datumCopy(dm, valueTypbyval, valueTyplen);                     \(void)MemoryContextSwitchTo(old_context);                                   \} while (0)/** 检查是否选择了特定的行进行分析* 1. 跳过已删除的行(死行)* 2. 如果已获取的行数 < 目标行数,则获取所有行* 3. 随机选择一个位置替换一个元组*/
#define check_match_tuple(copyTuple)                                              \do {                                                                          \(copyTuple) = false;                                                      \if (cstore->IsDeadRow((uint32)targblock, (uint32)targoffset)) {           \deadrows++;                                                           \} else if (numrows < targrows) {                                          \(copyTuple) = true;                                                   \location = numrows;                                                   \numrows++;                                                            \samplerows++;                                                         \} else {                                                                  \if (0 >= anl_get_next_S(samplerows, targrows, &rstate)) {             \location = (int64)(targrows * anl_random_fract());                \(copyTuple) = true;                                               \AssertEreport(location >= 0 && location < targrows, MOD_OPT, ""); \}                                                                     \samplerows++;                                                         \}                                                                         \} while (0)#define load_cu_data(s_col, e_col, values, nulls, copy)                                                       \do {                                                                                                      \for (int col1 = (s_col); col1 <= (e_col); ++col1) {                                                   \int colNum = attrSeq[col1];                                                                       \if (cuPtr[colNum]) {                                                                              \if (cuPtr[colNum]->IsNull(targoffset)) {                                                      \(nulls)[colNum] = true;                                                                   \(values)[colNum] = 0;                                                                     \} else {                                                                                      \(nulls)[colNum] = false;                                                                  \if (copy)                                                                                 \copy_sample_dataum((values)[colNum], colNum, targoffset);                             \else                                                                                      \(values)[colNum] = getValFuncPtr[colNum][funcIdx[colNum]](cuPtr[colNum], targoffset); \}                                                                                             \} else {                                                                                          \(nulls)[colNum] = nullValues[colNum];                                                         \(values)[colNum] = constValues[colNum];                                                       \}                                                                                                 \}                                                                                                     \} while (0)/** 试图加载所有的CU数据到cstore_buffers中:* 1. 如果总CU大小小于cstore_buffers的1/8,将所有CU加载到CU缓冲区。* 2. 如果总CU大小大于或等于cstore_buffers的1/8,将样本数据复制到临时内存以避免固定太多的CU。*/for (int col = 0; col < num_attnums; ++col) {int colNum = attrSeq[col];cuPtr[colNum] = NULL;slotIdList[col] = CACHE_BLOCK_INVALID_IDX;// 如果是当前处理的第一个列if (-1 == start_col)start_col = col;end_col = col;// 获取CU描述符(void)cstore->GetCUDesc(colNum, targblock, &cuDesc, tmpSnapshot);if (cuDesc.IsNullCU()) {nullValues[colNum] = true;constValues[colNum] = 0;elog(DEBUG2,"ANALYZE INFO - 表 \"%s\":attnum(%d),cuid(%u) 为空",RelationGetRelationName(onerel),colNum,targblock);continue;} else if (cuDesc.IsSameValCU()) {bool shoulFree = false;nullValues[colNum] = false;// 从CU描述符中获取常量值old_context = MemoryContextSwitchTo(sample_context);constValues[colNum] = CStore::CudescTupGetMinMaxDatum(&cuDesc, attrs[colNum], true, &shoulFree);(void)MemoryContextSwitchTo(old_context);elog(DEBUG2,"ANALYZE INFO - 表 \"%s\":attnum(%d),cuid(%u) 包含常量值",RelationGetRelationName(onerel),colNum,targblock);continue;} else {nullValues[colNum] = false;constValues[colNum] = 0;// 获取CU数据并缓存cuPtr[colNum] = cstore->GetCUData(&cuDesc, colNum, attrs[colNum]->attlen, slotIdList[col]);funcIdx[colNum] = cuPtr[colNum]->HasNullValue() ? 1 : 0;// 在每个CU的IO操作之前检查是否开启了Vacuum延迟。如果可以从CU缓存中获取CU数据,我们应该减少AMAP的调用次数。vacuum_delay_point();// 断言CU数据不是压缩的AssertEreport(!cuPtr[colNum]->m_cache_compressed, MOD_OPT, "");total_cusize += cuPtr[colNum]->GetUncompressBufSize();/** 如果总CU大小小于cusize_threshold并且(或者是最后一列,以确保所有样本CU数据都已加载到临时内存中),* 则继续下一列的处理。*/if (total_cusize < cusize_threshold && (first_batch || (num_attnums - 1) != col))continue;if (NIL == sampleTupleInfo) {ereport(LOG,(errmsg("ANALYZE INFO - 表 \"%s\":从CU缓冲区复制数据", RelationGetRelationName(onerel)),errdetail("增加cstore_buffers的大小以避免复制数据")));}elog(DEBUG2,"ANALYZE INFO - 表 \"%s\":复制列数据 [%d, %d] 从CU缓冲区",RelationGetRelationName(onerel),start_col,end_col);}all_in_buffer = false;if (first_batch) {/* 处理第一个批次的CU数据,我们应该首先初始化sample_tuple_cell */bool copyTuple = false;cell1 = list_head(sampleTupleInfo) ? list_head(sampleTupleInfo) : NULL;for (targoffset = FirstOffsetNumber - 1; targoffset < maxoffset; targoffset++) {check_match_tuple(copyTuple);if (!copyTuple)continue;if (cell1 != NULL) {errno_t rc;st_cell = (sample_tuple_cell*)lfirst(cell1);rc = memset_s(st_cell->nulls, sizeof(bool) * colTotalNum, true, sizeof(bool) * colTotalNum);securec_check(rc, "\0", "\0");rc = memset_s(st_cell->values, sizeof(Datum) * colTotalNum, 0, sizeof(Datum) * colTotalNum);securec_check(rc, "\0", "\0");} else {errno_t rc;st_cell = (sample_tuple_cell*)palloc0(sizeof(sample_tuple_cell));sampleTupleInfo = lappend(sampleTupleInfo, st_cell);st_cell->nulls = (bool*)palloc(sizeof(bool) * colTotalNum);rc = memset_s(st_cell->nulls, sizeof(bool) * colTotalNum, true, sizeof(bool) * colTotalNum);securec_check(rc, "\0", "\0");st_cell->values = (Datum*)palloc0(sizeof(Datum) * colTotalNum);}valueLocation = lappend_int(valueLocation, location);targoffsetList = lappend_int(targoffsetList, targoffset);// 加载CU数据load_cu_data(start_col, end_col, st_cell->values, st_cell->nulls, true);cell1 = (cell1 && lnext(cell1)) ? lnext(cell1) : NULL;}for (int col1 = start_col; col1 <= end_col; ++col1) {if (IsValidCacheSlotID(slotIdList[col1]))CUCache->UnPinDataBlock(slotIdList[col1]);}first_batch = false;AssertEreport(sampleTupleInfo, MOD_OPT, "");} else {/* 已经建立了sample_tuple_cell,只需将数据从缓冲区复制到临时内存中 */AssertEreport(sampleTupleInfo, MOD_OPT, "");forboth(cell1, sampleTupleInfo, cell3, targoffsetList){st_cell = (sample_tuple_cell*)lfirst(cell1);targoffset = lfirst_int(cell3);// 加载CU数据load_cu_data(start_col, end_col, st_cell->values, st_cell->nulls, true);}for (int col1 = start_col; col1 <= end_col; ++col1) {if (IsValidCacheSlotID(slotIdList[col1]))CUCache->UnPinDataBlock(slotIdList[col1]);}}start_col = -1;}if (all_in_buffer) {/* 已经将所有的CU加载到CU缓冲区,直接使用CU缓冲区数据构建元组 */bool copyTuple = false;// 断言已处理的列范围正确AssertEreport(0 == start_col && end_col == num_attnums - 1, MOD_OPT, "");for (targoffset = FirstOffsetNumber - 1; targoffset < maxoffset; targoffset++) {check_match_tuple(copyTuple);if (!copyTuple)continue;// 加载CU数据到元组load_cu_data(0, num_attnums - 1, values, nulls, false);tableam_tops_free_tuple(rows[location]);// 使用CU数据构建HeapTuplerows[location] = (HeapTuple)tableam_tops_form_tuple(onerel->rd_att, values, nulls, HEAP_TUPLE);ItemPointerSet(&(rows[location])->t_self, targblock, targoffset + 1);}for (int col1 = 0; col1 < num_attnums; ++col1) {if (IsValidCacheSlotID(slotIdList[col1]))CUCache->UnPinDataBlock(slotIdList[col1]);}} else {/* 使用临时内存中的数据构建元组 */AssertEreport(end_col == num_attnums - 1, MOD_OPT, "");forthree(cell1, sampleTupleInfo, cell2, valueLocation, cell3, targoffsetList){st_cell = (sample_tuple_cell*)lfirst(cell1);location = lfirst_int(cell2);targoffset = lfirst_int(cell3);tableam_tops_free_tuple(rows[location]);// 使用临时内存中的数据构建HeapTuplerows[location] = (HeapTuple)tableam_tops_form_tuple(onerel->rd_att, st_cell->values, st_cell->nulls, HEAP_TUPLE);ItemPointerSet(&(rows[location])->t_self, targblock, targoffset + 1);}// 释放临时列表list_free_ext(valueLocation);list_free_ext(targoffsetList);}// 重置内存上下文MemoryContextReset(sample_context);/* 步骤 4:释放内存 */pfree_ext(nullValues);pfree_ext(constValues);pfree_ext(nulls);pfree_ext(values);pfree_ext(attrSeq);pfree_ext(colIdx);pfree_ext(funcIdx);pfree_ext(cuPtr);pfree_ext(slotIdList);pfree_ext(getValFuncPtr);list_free_ext(sampleTupleInfo);pfree_ext(bms_attnums);/* 删除内存上下文 */MemoryContextDelete(sample_context);ADIO_RUN(){pfree_ext(anlprefetch.fetchlist1.blocklist);pfree_ext(anlprefetch.fetchlist2.blocklist);}ADIO_END();/* 步骤 5:关闭 delta 表并结束 cstore 扫描 */relation_close(deltarel, AccessShareLock);CStoreEndScan(cstoreScanDesc);if (estimate_table_rownum) {/* 在非 pretty 模式下估算总行数 */AssertEreport(!SUPPORT_PRETTY_ANALYZE, MOD_OPT, "");if (totalblocks == 0 || (totalblocks > 0 && liverows > 0)) {*totalrows = liverows * totalblocks + deltarows;} else {/* 目标 300 行的所有块都是死行。 */*totalrows = INVALID_ESTTOTALROWS + targrows;}ereport(elevel,(errmsg("ANALYZE INFO : \"%s\":扫描了 %u 个 CU,目标 cuid:%u,""包含 %ld 个活动行, %ld 个死亡行,估计总行数 %.0f",RelationGetRelationName(onerel),estBlkNum,totalblocks,targblock,liverows,deadrows,*totalrows)));return numrows + delta_numrows;}/** 步骤 6:对行进行排序并估算 reltuples** 如果我们没有找到所需数量的元组,则无需排序,因为它们已经有序。** 否则,我们需要根据位置(itempointer)对收集的元组进行排序。* 不值得担心已经有序的边界情况。*/if (numrows == targrows)qsort((void*)rows, numrows, sizeof(HeapTuple), compare_rows);/* 在数据节点中,输出一些有趣的关系信息 */if (IS_PGXC_DATANODE) {ereport(elevel,(errmsg("ANALYZE INFO : \"%s\":扫描了 %d 个 CU,样本 %ld 行,估算总 %.0f 行",RelationGetRelationName(onerel),bs.m,totalblocks,numrows,*totalrows)));}return numrows + delta_numrows;

  该函数的关键部分和作用如下:

  1. 获取样本数据 函数通过对表格的列存储块进行抽样,获取一定数量的行数据,这些行数据将用于后续的分析和统计
  2. 估算总行数和死行数 函数会估算列存储表格的总行数和死行数。总行数是表格中的所有行数,而死行数是已标记为删除的行数。
  3. 处理 delta 表如果表格具有 delta 表(差异表),函数会首先尝试从 delta 表中获取样本数据,以提高估算的准确性。
  4. 数据处理 函数根据样本数据中的 null 值、常量值等信息进行数据处理,包括处理列的数据类型和 null 值。
  5. 样本数据排序如果成功获取了样本数据,并且样本数据数量达到了目标数量,函数会对样本数据进行排序,以确保它们按照物理位置在表格中的顺序排列
  6. 输出结果函数会返回实际获取的样本数据行数,以及估算的总行数和死行数。这些信息将用于查询优化和统计信息收集。
  7. 其他功能函数还包括一些额外的功能,例如处理样本数据的方式(复制到临时内存或直接在 CU 缓冲区中操作)、IO 调度(根据优化器的要求进行数据加载)等。

es_get_attnums_to_analyze 函数

  es_get_attnums_to_analyze 函数用于获取需要进行分析的所有列的 attnum属性号),包括单列统计多列统计

  1. 参数:
  • vacattrstatsVacAttrStats 数组,包含要分析的列的统计信息。
  • vacattrstats_sizeVacAttrStats 数组的大小。
  1. 返回值:
  • Bitmapset*:一个位图集合,包含所有需要分析的列的 attnum。调试信息如下:
    在这里插入图片描述
  1. 函数逻辑:
  • 创建一个名为 bms_attnum位图集合,用于存储需要分析的列的 attnum
  • 使用两个嵌套的循环遍历 vacattrstats 数组和其中的每个 VacAttrStats 元素。
    (1)外循环遍历 vacattrstats 数组。
    (2)内循环遍历每个 VacAttrStats 元素的属性列表,属性列表存储在 attrs 成员中。
  • 在内循环中,获取每个属性的 attnum(属性号)并将其添加到 bms_attnum 中,使用 bms_add_member 函数。
  • 最终,函数返回一个位图集合 bms_attnum,其中包含了需要进行分析的所有列attnum

  函数源码如下:(路径:src/common/backend/utils/adt/extended_statistics.cpp

/** es_get_attnums_to_analyze*     get all attnums to be analyzed, including single column stats and multi-column stats** @param (in) vacattrstats:*     the VacAttrStats array* @param (in) vacattrstats_size:*     the size of the VacAttrStats array** @return:*     a Bitmapset including all columns' attnum*/
Bitmapset* es_get_attnums_to_analyze(VacAttrStats** vacattrstats, int vacattrstats_size)
{Bitmapset* bms_attnum = NULL;for (int i = 0; i < vacattrstats_size; ++i) {for (unsigned int j = 0; j < vacattrstats[i]->num_attrs; ++j) {int2 attnum = vacattrstats[i]->attrs[j]->attnum;bms_attnum = bms_add_member(bms_attnum, attnum);}}return bms_attnum;
}

CStoreRelGetCUNumByNow 函数

  CStoreRelGetCUNumByNow 函数用于获取一个列存储Column Store在当前时刻的压缩单元CU)数量

  1. 参数:
  • cstoreScanState列存储扫描描述符,包含了对列存储表的扫描状态信息
  1. 返回值:
  • uint32:表示当前时刻列存储表的压缩单元数量
  1. 函数逻辑:
  • 获取与列存储表关联的元组描述符TupleDesc)和关联Relation)。
  • 打开与列存储表关联的 CUDesc压缩单元描述)关系和其索引
    (1)cudesc_relCUDesc 表的关系
    (2)cudesc_tupdescCUDesc 表的元组描述符
    (3)idx_relCUDesc 表的索引关系
  • 获取列存储表的第一个属性的属性号 attid
    (1)如果第一个属性已经被删除(attisdropped 为真),则获取列存储表中第一个未被删除属性的属性号(fstColIdx 指示第一个未被删除属性的索引)。
  • 初始化用于检索 CUDesc 表的索引扫描键(ScanKey),以便按照属性号(col_id)查找对应的压缩单元描述。
  • 开始有序系统表扫描(SysScanDesc),以获取与指定属性号匹配的压缩单元描述
  • 初始化一个变量 max_cuid,用于存储找到的最大压缩单元编号。将其初始化为 FirstCUID 的值。
  • 通过反向扫描(BackwardScanDirection)系统表,查找匹配的压缩单元描述。如果找到匹配的描述,则将其压缩单元编号存储在 max_cuid 中。
  • 结束有序系统表扫描
  • 关闭 CUDesc 表的索引和关系。
  • 返回压缩单元数量,计算方式为 max_cuid - FirstCUID,即找到的最大压缩单元编号减去起始压缩单元编号 FirstCUID

  函数源码如下:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

// CStoreRelGetCUNumByNow
// 获取当前时刻关系的CU(压缩单元)数量uint32 CStoreRelGetCUNumByNow(CStoreScanDesc cstoreScanState)
{ScanKeyData key;HeapTuple tup;bool isnull = false;Relation relation = cstoreScanState->m_CStore->m_relation;/** 打开CUDesc关系和其索引。* CUDesc关系存储了列存储关系中每个列的CU(压缩单元)的描述信息,* 索引用于快速检索特定列的CU信息。*/Relation cudesc_rel = heap_open(relation->rd_rel->relcudescrelid, AccessShareLock);TupleDesc cudesc_tupdesc = cudesc_rel->rd_att;Relation idx_rel = index_open(cudesc_rel->rd_rel->relcudescidx, AccessShareLock);int attid = relation->rd_att->attrs[0]->attnum;// 如果列已删除(attisdropped),则选择关系的第一个非删除列。if (relation->rd_att->attrs[0]->attisdropped) {int fstColIdx = CStoreGetfstColIdx(relation);attid = relation->rd_att->attrs[fstColIdx]->attnum;}/* 设置扫描键以通过列ID从索引中获取数据。 */ScanKeyInit(&key, (AttrNumber)CUDescColIDAttr, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(attid));// 开始有序扫描CUDesc关系以获取与列相关的最后一个CU描述。SysScanDesc cudesc_scan = systable_beginscan_ordered(cudesc_rel, idx_rel, SnapshotNow, 1, &key);uint32 max_cuid = FirstCUID;/** 优化以获取列的最后一个CU(压缩单元)描述。* 使用BackwardScanDirection扫描以获取最新的CU描述信息。*/if ((tup = systable_getnext_ordered(cudesc_scan, BackwardScanDirection)) != NULL) {max_cuid = DatumGetUInt32(fastgetattr(tup, CUDescCUIDAttr, cudesc_tupdesc, &isnull));}systable_endscan_ordered(cudesc_scan);index_close(idx_rel, AccessShareLock);heap_close(cudesc_rel, AccessShareLock);// 返回最后一个CU的ID,并将其与FirstCUID相减以获得CU的数量。return (max_cuid - FirstCUID);
}

  该函数通常在列存储数据库的查询优化和统计信息收集过程中使用,以了解关系中当前 CU 的数量。

CStore::GetLivedRowNumbers 函数

  CStore::GetLivedRowNumbers 函数的作用是计算 CStore 存储引擎中指定关系的存活(未删除)行数,并提供了总删除行数。这个函数通过遍历列存储单元CU)的描述信息来实现这一目标。

代码的主要步骤如下:

  1. 初始化变量 rowNumbers0,该变量用于存储存活行数
  2. 初始化变量 * totaldeadrows0,该变量用于存储已删除行数
  3. 使用 LoadCUDescCtl 对象 loadInfo 初始化,该对象用于加载CU(压缩单元)的描述信息。
  4. 进入循环,通过 GetCURowCount 函数获取CU的行数信息,并存储在 loadInfo 中。
  5. 遍历每个CU的描述信息,获取其行数以及检查是否存在已删除的行。
  6. 如果存在已删除的行,通过计算位掩码中设置的位数来更新存活行数和已删除行数。
  7. 继续加载下一个CU的描述信息,直到所有CU都被处理。
  8. 销毁 loadInfo 对象,释放相关资源。
  9. 返回存活行数

  其函数源码如下:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

/** 获取CStore关系的存活行数。* 该函数计算CStore关系中存活(未删除)行的数量,* 并提供总删除行数。** @param (out) totaldeadrows:*     指向存储总删除行数的变量的指针。** @return:*     CStore关系中存活(未删除)行的数量。*/
int64 CStore::GetLivedRowNumbers(int64* totaldeadrows)
{int64 rowNumbers = 0;  // 初始化变量以存储存活行数。LoadCUDescCtl loadInfo(m_startCUID);  // 创建用于加载CU描述符的控制对象。*totaldeadrows = 0;  // 将总删除行数初始化为零。// 遍历CU描述符。while (GetCURowCount(m_firstColIdx, &loadInfo, m_snapshot)) {CUDesc* cuDescArray = loadInfo.cuDescArray;// 遍历当前加载的CU描述符数组。for (uint32 i = 0; i < loadInfo.curLoadNum; ++i) {GetCUDeleteMaskIfNeed(cuDescArray[i].cu_id, m_snapshot);// 增加存活行数。rowNumbers += cuDescArray[i].row_count;if (m_hasDeadRow) {int nBytes = (cuDescArray[i].row_count + 7) / 8;// 遍历位图字节,计算总删除行数。for (int j = 0; j < nBytes; ++j) {*totaldeadrows += NumberOfBit1Set[m_cuDelMask[j]];rowNumbers -= NumberOfBit1Set[m_cuDelMask[j]];}}}}loadInfo.Destroy();

InitGetValFunc 函数

  InitGetValFunc 函数的主要作用是根据数据类型的字节长度 attlen 初始化一个函数指针数组 getValFuncPtr用于获取不同数据类型的值。这个函数在分析过程中非常有用,因为它允许针对不同的数据类型选择正确的函数来获取列数据,以确保数据的正确性高效性

具体来说,这个函数的作用如下:

  1. 根据传入的 attlen 参数,确定数据类型的字节长度,以便后续选择合适的函数来处理数据。
  2. 根据数据类型的字节长度,为该列初始化两个函数指针,一个用于处理带符号的数据,另一个用于处理无符号的数据。这些函数指针会存储在 getValFuncPtr 数组中的特定列中。
  3. 这些函数指针在分析期间用于获取列数据根据列的数据类型和是否带符号,选择适当的函数指针,以确保正确地获取数据值。
  4. 如果传入的 attlen 值不匹配任何已知的数据类型,函数会引发错误,报告不支持的数据类型。
    总结:InitGetValFunc 函数为分析过程提供了必要的工具,以根据不同的数据类型获取正确的列数据,确保了数据分析的准确性和效率

  其函数源码如下:(路径:src/gausskernel/optimizer/commands/analyze.cpp

void InitGetValFunc(int attlen, GetValFunc* getValFuncPtr, int col)
{switch (attlen) {case sizeof(uint8): {getValFuncPtr[col][0] = &GetValue<1, false>;getValFuncPtr[col][1] = &GetValue<1, true>;break;}case sizeof(uint16): {getValFuncPtr[col][0] = &GetValue<2, false>;getValFuncPtr[col][1] = &GetValue<2, true>;break;}case sizeof(uint32): {getValFuncPtr[col][0] = &GetValue<4, false>;getValFuncPtr[col][1] = &GetValue<4, true>;break;}case sizeof(uint64): {getValFuncPtr[col][0] = &GetValue<8, false>;getValFuncPtr[col][1] = &GetValue<8, true>;break;}case 12: {getValFuncPtr[col][0] = &GetValue<12, false>;getValFuncPtr[col][1] = &GetValue<12, true>;break;}case 16: {getValFuncPtr[col][0] = &GetValue<16, false>;getValFuncPtr[col][1] = &GetValue<16, true>;break;}case -1: {getValFuncPtr[col][0] = &GetValue<-1, false>;getValFuncPtr[col][1] = &GetValue<-1, true>;break;}case -2: {getValFuncPtr[col][0] = &GetValue<-2, false>;getValFuncPtr[col][1] = &GetValue<-2, true>;break;}default:ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("unsupported datatype")));}
}

CStoreGetfstColIdx 函数

  CStoreGetfstColIdx 函数的主要功能是遍历给定关系 rel 的所有列,并找到第一个未被删除(未被标记为 dropped)的列,然后返回该列的索引(列号)。这个函数通常用于在处理表或关系的列时,需要忽略已删除的列,从而准确地获取第一个有效的列。如果没有未被删除的列,它将返回 0,表示第一列未被删除。其函数源码如下:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

/** @Description: get the first colum index that is not dropped* @Param[IN] rel: the target relation* @Return: the first column index that is not dropped* @See also:*/
int CStoreGetfstColIdx(Relation rel)
{for (int i = 0; i < rel->rd_att->natts; i++) {if (!rel->rd_att->attrs[i]->attisdropped)return i;}return 0;
}

CStore::GetCUDesc 函数

  CStore::GetCUDesc 函数的作用是根据给定的列号col)和 CU IDcuid)获取列的 CUDesc(列存储单元描述)。它主要完成以下任务:

  1. 初始化相关变量和上下文。
  2. 打开 CUDesc 表和其索引
  3. 设置用于索引扫描扫描键
  4. 执行索引扫描,找到匹配的 CUDesc 记录。
  5. 解析 CUDesc 记录,提取其中的信息并存储到 cuDescPtr 结构中。
  6. 关闭相关的数据库对象,释放资源
  7. 返回是否找到匹配的 CUDesc 记录。如果找到,返回 true;否则,返回 false

  其函数源码如下:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

/** Get CUDesc of column according to cuid.* 根据 cuid 获取列的 CUDesc(列存储单元描述)。*/
bool CStore::GetCUDesc(_in_ int col, _in_ uint32 cuid, _out_ CUDesc* cuDescPtr, _in_ Snapshot snapShot)
{ScanKeyData key[2];HeapTuple tup;bool found = false;errno_t rc = EOK;Assert(col >= 0);// 当切换到下一批 cudesc 数据时,我们将重置 m_perScanMemCnxt。// 因此,仅在此批次中使用的空间应由 m_perScanMemCnxt 管理。AutoContextSwitch newMemCnxt(m_perScanMemCnxt);/** 打开 CUDesc 关系和其索引*/Relation cudesc_rel = heap_open(m_relation->rd_rel->relcudescrelid, AccessShareLock);TupleDesc cudesc_tupdesc = cudesc_rel->rd_att;Relation idx_rel = index_open(cudesc_rel->rd_rel->relcudescidx, AccessShareLock);bool isFixedLen = m_relation->rd_att->attrs[col]->attlen > 0 ? true : false;// 转换逻辑 ID 为属性的物理 IDint attid = m_relation->rd_att->attrs[col]->attnum;/** 设置扫描键以通过 attid 从索引中获取。*/ScanKeyInit(&key[0], (AttrNumber)CUDescColIDAttr, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(attid));ScanKeyInit(&key[1], (AttrNumber)CUDescCUIDAttr, BTEqualStrategyNumber, F_OIDEQ, UInt32GetDatum(cuid));snapShot = (snapShot == NULL) ? GetActiveSnapshot() : snapShot;Assert(snapShot != NULL);SysScanDesc cudesc_scan = systable_beginscan_ordered(cudesc_rel, idx_rel, snapShot, 2, key);// 仅循环一次while ((tup = systable_getnext_ordered(cudesc_scan, ForwardScanDirection)) != NULL) {Datum values[CUDescCUExtraAttr] = {0};bool isnull[CUDescCUExtraAttr] = {0};char* valPtr = NULL;heap_deform_tuple(tup, cudesc_tupdesc, values, isnull);uint32 cu_id = DatumGetUInt32(values[CUDescCUIDAttr - 1]);Assert(!isnull[CUDescCUIDAttr - 1] && cu_id == cuid && found == false);cuDescPtr->xmin = HeapTupleGetRawXmin(tup);cuDescPtr->cu_id = cu_id;// 将最小值放入 cudesc->cu_minif (!isnull[CUDescMinAttr - 1]) {char* minPtr = cuDescPtr->cu_min;char len_1 = MIN_MAX_LEN;valPtr = DatumGetPointer(values[CUDescMinAttr - 1]);if (!isFixedLen) {*minPtr = (char)VARSIZE_ANY_EXHDR(valPtr);minPtr = minPtr + 1;len_1 -= 1;}rc = memcpy_s(minPtr, len_1, VARDATA_ANY(valPtr), VARSIZE_ANY_EXHDR(valPtr));securec_check(rc, "", "");}// 将最大值放入 cudesc->cu_maxif (!isnull[CUDescMaxAttr - 1]) {char* maxPtr = cuDescPtr->cu_max;char len_2 = MIN_MAX_LEN;valPtr = DatumGetPointer(values[CUDescMaxAttr - 1]);if (!isFixedLen) {*maxPtr = VARSIZE_ANY_EXHDR(valPtr);maxPtr = maxPtr + 1;len_2 -= 1;}rc = memcpy_s(maxPtr, len_2, VARDATA_ANY(valPtr), VARSIZE_ANY_EXHDR(valPtr));securec_check(rc, "", "");}cuDescPtr->row_count = DatumGetInt32(values[CUDescRowCountAttr - 1]);Assert(!isnull[CUDescRowCountAttr - 1]);// 将 CUMode 放入 cudesc->cumodecuDescPtr->cu_mode = DatumGetInt32(values[CUDescCUModeAttr - 1]);Assert(!isnull[CUDescCUModeAttr - 1]);// 将 cusize 放入 cudesc->cu_sizecuDescPtr->cu_size = DatumGetInt32(values[CUDescSizeAttr - 1]);Assert(!isnull[CUDescSizeAttr - 1]);// 将 CUPointer 放入 cudesc->cuPointerchar* cu_ptr = DatumGetPointer(values[CUDescCUPointerAttr - 1]);Assert(!isnull[CUDescCUPointerAttr - 1] && cu_ptr);rc = memcpy_s(&cuDescPtr->cu_pointer, sizeof(CUPointer), VARDATA_ANY(cu_ptr), sizeof(CUPointer));securec_check(rc, "", "");Assert(VARSIZE_ANY_EXHDR(cu_ptr) == sizeof(CUPointer));cuDescPtr->magic = DatumGetUInt32(values[CUDescCUMagicAttr - 1]);Assert(!isnull[CUDescCUMagicAttr - 1]);found = true;}systable_endscan_ordered(cudesc_scan);index_close(idx_rel, AccessShareLock);heap_close(cudesc_rel, AccessShareLock);return found;
}

CStore::IsTheWholeCuDeleted 函数

  CStore::IsTheWholeCuDeleted 函数的作用是根据给定的 cuid(列存储单元 ID)加载相应的删除掩码,以标识是否存在已删除的行。它完成以下任务:

  1. 初始化相关变量。
  2. 如果已加载相同的删除掩码,直接返回,无需再次加载。
  3. 在进行扫描之前,切换到新的内存上下文以管理分配的内存。
  4. 打开 CUDesc 关系和其索引,以准备执行索引扫描。
  5. 设置用于索引扫描的扫描键。
  6. 如果未提供快照,则使用当前活动快照。
  7. 开始按顺序扫描 CUDesc 关系,查找匹配的记录。
  8. 如果找到匹配的记录,则提取 CUPointer(列存储单元指针)并相应地处理删除掩码。
  9. 结束索引扫描并关闭相关的数据库对象。
  10. 处理异常情况

  函数源码如下:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

ull = false;
errno_t rc = EOK;
bool found = false;// 如果删除掩码已加载,无需再次加载,直接返回
if (m_delMaskCUId == cuid)return;// 当切换到下一批 cudesc 数据时,我们将重置 m_perScanMemCnxt。
// 因此,仅在此批次中使用的空间应由 m_perScanMemCnxt 管理。
AutoContextSwitch newMemCnxt(m_perScanMemCnxt);// 打开 CUDesc 关系和其索引
Relation cudesc_rel = heap_open(m_relation->rd_rel->relcudescrelid, AccessShareLock);
TupleDesc cudesc_tupdesc = cudesc_rel->rd_att;
Relation idx_rel = index_open(cudesc_rel->rd_rel->relcudescidx, AccessShareLock);// 设置扫描键以通过 attid 从索引中获取。
ScanKeyInit(&key[0], (AttrNumber)CUDescColIDAttr, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(VitrualDelColID));ScanKeyInit(&key[1], (AttrNumber)CUDescCUIDAttr, BTEqualStrategyNumber, F_OIDEQ, UInt32GetDatum(cuid));// 如果未提供快照,则使用当前活动快照
snapShot = (snapShot == NULL) ? GetActiveSnapshot() : snapShot;
Assert(snapShot != NULL);// 开始按顺序扫描 cudesc 关系
SysScanDesc cudesc_scan = systable_beginscan_ordered(cudesc_rel, idx_rel, snapShot, 2, key);if ((tup = systable_getnext_ordered(cudesc_scan, ForwardScanDirection)) != NULL) {// 将 CUPointer 放入 cudesc->cuPointerDatum v = fastgetattr(tup, CUDescCUPointerAttr, cudesc_tupdesc, &isnull);if (isnull)m_hasDeadRow = false;else {m_hasDeadRow = true;int8* bitmap = (int8*)PG_DETOAST_DATUM(DatumGetPointer(v));rc = memcpy_s(m_cuDelMask, MaxDelBitmapSize, VARDATA_ANY(bitmap), VARSIZE_ANY_EXHDR(bitmap));securec_check(rc, "", "");// 因为可能创建了新的内存,所以我们必须检查并及时释放。if ((Pointer)bitmap != DatumGetPointer(v)) {pfree_ext(bitmap);}}found = true;
}systable_endscan_ordered(cudesc_scan);
index_close(idx_rel, AccessShareLock);
heap_close(cudesc_rel, AccessShareLock);// 如果未找到对应的删除掩码,则处理异常情况
if (!found) {TransactionId currGlobalXmin = pg_atomic_read_u64(&t_thrd.xact_cxt.ShmemVariableCache->recentGlobalXmin);Assert(snapShot->xmin > 0);if (TransactionIdPrecedes(snapShot->xmin, currGlobalXmin))ereport(ERROR,(errcode(ERRCODE_SNAPSHOT_INVALID),(errmsg("Snapshot too old."),errdetail("Could not get the old version of CUDeleteBitmap, RecentGlobalXmin: %lu, ""snapShot->xmin: %lu, snapShot->xmax: %lu",currGlobalXmin,snapShot->xmin,snapShot->xmax),errhint("This is a safe error report, will not impact data consistency, retry your query if ""needed."))));else {if (m_useBtreeIndex)m_delMaskCUId = InValidCUID;else {// 处理异常情况:CU 删除位图丢失ereport(PANIC,(errmsg("CU Delete bitmap is missing."),errdetail("There might be some issue about cu %u delete bitmap, Please contact HW engineers ""for support.",cuid)));}}
} else {// 记录已加载的删除掩码的 cuidm_delMaskCUId = cuid;
}return;
}

CStore::IsTheWholeCuDeleted 函数

   CStore::IsTheWholeCuDeleted 函数的作用是 判断指定的列存储单元(CU)是否已经完全删除。函数接受一个参数 rowsInCu,表示列存储单元中的行数。

函数内部的逻辑如下:

  1. 首先,它检查成员变量 m_hasDeadRow 是否为 true,这个变量表示当前列存储中是否包含已删除的行。如果没有已删除的行,那么整个列存储单元就不可能被删除。
  2. 如果 m_hasDeadRowtrue,则调用 IsTheWholeCuDeleted 函数来进一步检查列存储单元的删除状态。它将 m_cuDelMask删除掩码)和 rowsInCu 作为参数传递给 IsTheWholeCuDeleted 函数,以确定列存储单元是否已完全删除

   其中,函数 IsTheWholeCuDeleted 被定义为两个版本,一个版本接受 int 类型的参数,另一个版本接受 char* 类型的参数。目的是优化性能,当没有已删除的行时,不需要执行更复杂的检查,直接返回结果。只有在存在已删除的行时,才需要进一步检查删除状态。
   函数源码如下:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

bool CStore::IsTheWholeCuDeleted(int rowsInCu)
{return m_hasDeadRow && IsTheWholeCuDeleted((char*)m_cuDelMask, rowsInCu);
}
/*** @Description: 检查该CU内的所有元组是否已删除。*               如果是,则返回true;否则返回false。* @param rowsInCu: CU内包含的元组数量* @param delBitmapPtr: 删除位图* @return: 如果该位图内的所有元组都已删除,则返回true。*          如果有任何一个元组是活跃的,则返回false。* @See also:*/
bool CStore::IsTheWholeCuDeleted(char* delBitmapPtr, int rowsInCu)
{// 预先计算用于快速比较的映射数组static const uint8 map[] = {0, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF};unsigned int numUint64 = 0;unsigned int numUint8 = 0;unsigned int mapIdx = 0;/** numUint64 表示当值的数量为 rowsInCu 时,使用多少个 Uint64 数据;* 这等于 (rowsInCu/64),因为 Uint64 可以保存 64 位。* numUint8 表示使用多少个 Uint8 数据,不包括 (numUint64 * 64) 个值;* 这等于 ((rowsInCu - numUint64 * 64) / 8)。同时我们排除了最后半字节。*/compute_factors_of_n((unsigned int)rowsInCu, numUint64, numUint8, mapIdx);/* 通过将 *delBitmapPtr* 视为 uint64 数组来进行快速比较。*/uint64* uint64Item = (uint64*)delBitmapPtr;for (unsigned int i = 0; i < numUint64; ++i) {if (*uint64Item != 0xFFFFFFFFFFFFFFFF) {return false;}++uint64Item;}/* 将剩余部分视为 char 数组来进行比较。*/uint8* uint8Item = (uint8*)uint64Item;for (unsigned int i = 0; i < numUint8; ++i) {if (*uint8Item != 0xFF) {return false;}++uint8Item;}/** 如果 (rowsInCu != 8*N),则必须特殊处理最后一个字节。* 我们将使用 *map[]* 直接进行快速比较。*/if (mapIdx != 0) {return (*uint8Item == map[mapIdx]);}/* 没问题,整个 CU 已被删除。 */return true;
}

CStore::CudescTupGetMinMaxDatum 函数

  CStore::CudescTupGetMinMaxDatum 函数的作用是从 CUDesc 结构中提取并返回最小或最大值的 Datum 表示。该函数针对不同的列属性信息pColAttr)以及最小或最大值标志min),处理不同数据类型和大小的数据,并返回一个 Datum 表示。函数还通过 shouldFree 参数返回一个布尔值,指示是否应该释放提取的数据。函数源码如下:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

/*** @Description: 从CUDesc结构中提取并返回最小或最大值的Datum表示。*               该函数根据提供的列属性信息,处理不同的数据类型和大小。* @param pCudesc: CUDesc结构指针,包含了最小和最大值* @param pColAttr: 列属性的指针,描述了列的数据类型和大小* @param min: 为true表示提取最小值,为false表示提取最大值* @param shouldFree: 返回一个布尔值,指示是否应该释放提取的数据* @return: 表示最小或最大值的Datum* @See also:*/
Datum CStore::CudescTupGetMinMaxDatum(_in_ CUDesc* pCudesc, _in_ Form_pg_attribute pColAttr, _in_ bool min, _out_ bool* shouldFree)
{// 确保CUDesc结构表示了相同的值(即min和max相同)Assert(pCudesc->IsSameValCU());*shouldFree = false; // 默认情况下不需要释放数据char* value = NULL;char* dataPtr = min ? pCudesc->cu_min : pCudesc->cu_max; // 根据需要选择最小或最大值的指针errno_t rc = EOK;if (pColAttr->attbyval) {// 情况1:attlen > 0 && attlen <= sizeof(Datum)// 数据可以直接按值传递return (*(Datum*)dataPtr);}*shouldFree = true; // 数据需要释放if (pColAttr->attlen > (int)sizeof(Datum)) {// 情况2:attlen > sizeof(Datum) && attlen <= MIN_MAX_LENAssert(pColAttr->attlen <= MIN_MAX_LEN);value = (char*)palloc(pColAttr->attlen); // 为数据分配内存rc = memcpy_s(value, pColAttr->attlen, dataPtr, pColAttr->attlen); // 复制数据securec_check(rc, "", "");} else if (pColAttr->attlen == -1) {// 情况3:attlen == -1,包括空字符串(非空字符串)Assert((int)dataPtr[0] >= 0 && (int)dataPtr[0] < MIN_MAX_LEN);value = (char*)palloc(dataPtr[0] + VARHDRSZ_SHORT); // 为字符串数据分配内存SET_VARSIZE_SHORT(value, dataPtr[0] + VARHDRSZ_SHORT); // 设置字符串头部大小if (dataPtr[0] > 0) {rc = memcpy_s(value + VARHDRSZ_SHORT, dataPtr[0], dataPtr + 1, dataPtr[0]); // 复制字符串数据securec_check(rc, "", "");}} else {// 情况4:attlen == -2,包括非空字符串Assert((int)dataPtr[0] > 0 && (int)dataPtr[0] < MIN_MAX_LEN);Assert(dataPtr[(int)dataPtr[0]] == '\0');value = (char*)palloc(dataPtr[0]); // 为字符串数据分配内存rc = memcpy_s(value, dataPtr[0], dataPtr + 1, dataPtr[0]); // 复制字符串数据securec_check(rc, "", "");}return PointerGetDatum(value); // 返回表示最小或最大值的Datum
}

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

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

相关文章

vue开发-语法和基础流程规范

当一个请求发生的时候 后端的扭转流程对标前端的扭转过程 ***vue 中 整体url触发整体流程服务流程&#xff1a; node 中定义了默认加载 vue.config.js 文件 &#xff08;vue.config.js 文件名不可更改&#xff09;-> vue.config.js 中 devServer 绑定了个 ip端口 和资源 -》…

Java版本企业工程行业管理系统源码

Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下&#xff1a; 首页 工作台&#xff1a;待办工作、消息通知、预警信息&#xff0c;点击可进入相应的列表 项目进度图表&#xff1a;选择&#xff08;总体或单个&#xff09;项目显示1…

初学Python记

Python这个编程语言的大名当然听说过了呀&#xff0c;这几年特别火&#xff0c;火的一塌涂地。大家可以回忆一下&#xff1a;朋友圈推荐的广告里经常可以看见python的网课广告。 本学期&#xff0c;学校开设了python课程&#xff0c;这几天学习了一下入了一下门&#xff0c;感…

OpenHarmony:如何使用HDF驱动控制LED灯

一、程序简介 该程序是基于OpenHarmony标准系统编写的基础外设类&#xff1a;RGB LED。 目前已在凌蒙派-RK3568开发板跑通。详细资料请参考官网&#xff1a;https://gitee.com/Lockzhiner-Electronics/lockzhiner-rk3568-openharmony/tree/master/samples/b02_hdf_rgb_led。 …

C++ 判断

C 判断 判断结构要求程序员指定一个或多个要评估或测试的条件&#xff0c;以及条件为真时要执行的语句&#xff08;必需的&#xff09;和条件为假时要执行的语句&#xff08;可选的&#xff09;。 下面是大多数编程语言中典型的判断结构的一般形式&#xff1a; 判断语句 C 编…

SpringMVC之前端增删改查实现

SpringMVC是一个基于MVC架构的框架&#xff0c;它可以帮助我们实现前后端的分离&#xff0c;同时也能很好地支持前端的增删改查操作。 配置 Cloudjun <?xml version"1.0" encoding"UTF-8" ?><taglib xmlns"http://java.sun.com/xml/ns/j…

TSINGSEE青犀/视频分析/边缘计算/AI算法·人员/区域入侵功能——多场景高效运用

TSINGSEE青犀视频人员/区域入侵功能可对重要区域进行实时监测&#xff0c;对监控区域进行7*24全天候管控&#xff0c;当监测到有人员靠近、闯入时&#xff0c;AI算法后台就会立即发出告警及时通知管理人员&#xff0c;变被动“监督”为主动“监控”&#xff0c;真正做到事前预警…

2023计算机毕业设计题目 毕设选题大全

文章目录 0 前言1 java web 管理系统 毕设选题2 java web 平台/业务系统 毕设选题3 游戏设计、动画设计类 毕设选题 (适合数媒的同学)4 算法开发5 数据挖掘 毕设选题6 大数据处理、云计算、区块链 毕设选题7 网络安全 毕设选题8 通信类/网络工程 毕设选题9 嵌入式 毕设选题10 开…

Newman+Jenkins实现接口自动化测试

一、是什么Newman Newman就是纽曼手机这个经典牌子&#xff0c;哈哈&#xff0c;开玩笑啦。。。别当真&#xff0c;简单地说Newman就是命令行版的Postman&#xff0c;查看官网地址。 Newman可以使用Postman导出的collection文件直接在命令行运行&#xff0c;把Postman界面化运…

uniapp项目实践总结(十三)封装文件操作方法

导语&#xff1a;在日常 APP 开发过程中&#xff0c;经常要进行文件的保存、读取列表以及查看和删除文件等操作&#xff0c;接下来就看一下具体的方法。 目录 原理分析方法实现实战演练案例展示 原理分析 主要是以下 API。 uni.saveFile&#xff1a;保存文件到本地缓存列表…

小白备战大厂算法笔试(四)——哈希表

文章目录 哈希表常用操作简单实现冲突与扩容链式地址开放寻址线性探测多次哈希 哈希表 哈希表&#xff0c;又称散列表&#xff0c;其通过建立键 key 与值 value 之间的映射&#xff0c;实现高效的元素查询。具体而言&#xff0c;我们向哈希表输入一个键 key &#xff0c;则可以…

K210-CanMV IDE开发软件

K210-CanMV IDE开发软件 界面功能简介连接设备临时运行开机运行程序 界面功能简介 区域①菜单栏&#xff1a;操作文件&#xff0c;使用工具等。 区域②快捷按钮&#xff1a;区域①中的文件和编辑中部分功能的快捷方式。 区域③连接设备&#xff1a;连接设备和程序控制按钮。 …

2023-9-8 求组合数(二)

题目链接&#xff1a;求组合数 II #include <iostream> #include <algorithm>using namespace std;typedef long long LL; const int mod 1e9 7; const int N 100010;// 阶乘&#xff0c;阶乘的逆 int fact[N], infact[N];LL qmi(int a, int k, int p) {int res…

基于单片机压力传感器MPX4115检测-报警系统-proteus仿真-源程序

一、系统方案 本设计采用52单片机作为主控器&#xff0c;液晶1602显示&#xff0c;MPX4115检测压力&#xff0c;按键设置报警&#xff0c;LED报警。 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 /***************************************…

分布式秒杀方案--java

前提&#xff1a;先把商品详情和秒杀商品缓存redis中&#xff0c;减少对数据库的访问&#xff08;可使用定时任务&#xff09; 秒杀商品无非就是那几步&#xff08;前面还可能会有一些判断&#xff0c;如用户是否登录&#xff0c;一人一单&#xff0c;秒杀时间验证等&#xff0…

OpenCV 03(数据结构--Mat)

一、Mat介绍 Mat是OpenCV在C语言中用来表示图像数据的一种数据结构.在python中转化为numpy的ndarray. Mat由header和data组成, header中记录了图片的维数, 大小, 数据类型等数据. 1.1 Mat拷贝 - Mat共享数据 在python中Mat数据对应numpy的ndarray, 使用numpy提供的深浅拷贝方…

C++——类与对象(下篇)

前言 前面已经介绍了类与对象&#xff08;上&#xff09;&#xff0c;类与对象&#xff08;中&#xff09;的两篇文章&#xff0c;下面是类与对象的最后一些重要知识点的介绍和总结。 目录 再谈构造函数Static成员友元内部类匿名对象拷贝对象时的一些编译器优化再次理解封装…

使用wkhtmltoimage实现生成长图分享

需求 用户可以选择以长图的形式分享本网页 方法 wkhtmltopdf wkhtmltopdf url filewkhtmltoimage url file java Runtime.getRuntime().exec() 下载 直接去官网下载对应的版本&#xff1a;官网 命令行使用WK > wkhtmltopdf https://www.nowcoder.com /opt/project/…

redis高可用——主从复制、哨兵模式、cluster集群

1、redis群集有三种模式 分别是主从同步/复制、哨兵模式、Cluster&#xff0c;下面会讲解一下三种模式的工作方式&#xff0c;以及如何搭建cIustr群集 主从复制:主从复制是高可用Redis的基础&#xff0c;哨兵和集群都是在主从复制基础上实现高可用的。主从复制主要实现了数据的…

功能测试常用的测试用例大全

登录、添加、删除、查询模块是我们经常遇到的&#xff0c;这些模块的测试点该如何考虑 1)登录 ① 用户名和密码都符合要求(格式上的要求) ② 用户名和密码都不符合要求(格式上的要求) ③ 用户名符合要求&#xff0c;密码不符合要求(格式上的要求) ④ 密码符合要求&#xff0c;…