列存储(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
。
函数的入参含义如下:
- Relation onerel: 被分析的关系(表)的 Relation 结构,表示要进行样本抽样的目标表。调试信息如下:
- int elevel: 日志消息的错误级别。用于确定错误消息的日志级别。
- HeapTuple* rows: 一个用于存储样本数据的数组。函数将抽样的行数据存储在这个数组中。
- int64 targrows: 目标抽样行数,即希望从表中抽取的行数。
- double* totalrows: 用于存储表的估计总行数的指针。函数将估计的总行数存储在这个指针指向的变量中。
- double* totaldeadrows: 用于存储表的估计死行数的指针。函数将估计的死行数存储在这个指针指向的变量中。
- VacAttrStats** vacattrstats: 一个 VacAttrStats 结构的数组,表示用于分析的每个属性的统计信息。这些统计信息包括样本数据的属性分布等。
- 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;
该函数的关键部分和作用如下:
- 获取样本数据: 函数通过对表格的列存储块进行抽样,获取一定数量的行数据,这些行数据将用于后续的分析和统计。
- 估算总行数和死行数: 函数会估算列存储表格的总行数和死行数。总行数是表格中的所有行数,而死行数是已标记为删除的行数。
- 处理 delta 表: 如果表格具有 delta 表(差异表),函数会首先尝试从 delta 表中获取样本数据,以提高估算的准确性。
- 数据处理: 函数根据样本数据中的 null 值、常量值等信息进行数据处理,包括处理列的数据类型和 null 值。
- 样本数据排序: 如果成功获取了样本数据,并且样本数据数量达到了目标数量,函数会对样本数据进行排序,以确保它们按照物理位置在表格中的顺序排列。
- 输出结果: 函数会返回实际获取的样本数据行数,以及估算的总行数和死行数。这些信息将用于查询优化和统计信息收集。
- 其他功能: 函数还包括一些额外的功能,例如处理样本数据的方式(复制到临时内存或直接在 CU 缓冲区中操作)、IO 调度(根据优化器的要求进行数据加载)等。
es_get_attnums_to_analyze 函数
es_get_attnums_to_analyze 函数用于获取需要进行分析的所有列的 attnum(属性号),包括单列统计和多列统计。
- 参数:
- vacattrstats:VacAttrStats 数组,包含要分析的列的统计信息。
- vacattrstats_size:VacAttrStats 数组的大小。
- 返回值:
- Bitmapset*:一个位图集合,包含所有需要分析的列的 attnum。调试信息如下:
- 函数逻辑:
- 创建一个名为 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)数量。
- 参数:
- cstoreScanState:列存储扫描描述符,包含了对列存储表的扫描状态信息。
- 返回值:
- uint32:表示当前时刻列存储表的压缩单元数量。
- 函数逻辑:
- 获取与列存储表关联的元组描述符(TupleDesc)和关联(Relation)。
- 打开与列存储表关联的 CUDesc(压缩单元描述)关系和其索引。
(1)cudesc_rel:CUDesc 表的关系。
(2)cudesc_tupdesc:CUDesc 表的元组描述符。
(3)idx_rel:CUDesc 表的索引关系。- 获取列存储表的第一个属性的属性号 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)的描述信息来实现这一目标。
代码的主要步骤如下:
- 初始化变量 rowNumbers 为0,该变量用于存储存活行数。
- 初始化变量 * totaldeadrows 为0,该变量用于存储已删除行数。
- 使用 LoadCUDescCtl 对象 loadInfo 初始化,该对象用于加载CU(压缩单元)的描述信息。
- 进入循环,通过 GetCURowCount 函数获取CU的行数信息,并存储在 loadInfo 中。
- 遍历每个CU的描述信息,获取其行数以及检查是否存在已删除的行。
- 如果存在已删除的行,通过计算位掩码中设置的位数来更新存活行数和已删除行数。
- 继续加载下一个CU的描述信息,直到所有CU都被处理。
- 销毁 loadInfo 对象,释放相关资源。
- 返回存活行数。
其函数源码如下:(路径: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,用于获取不同数据类型的值。这个函数在分析过程中非常有用,因为它允许针对不同的数据类型选择正确的函数来获取列数据,以确保数据的正确性和高效性。
具体来说,这个函数的作用如下:
- 根据传入的 attlen 参数,确定数据类型的字节长度,以便后续选择合适的函数来处理数据。
- 根据数据类型的字节长度,为该列初始化两个函数指针,一个用于处理带符号的数据,另一个用于处理无符号的数据。这些函数指针会存储在 getValFuncPtr 数组中的特定列中。
- 这些函数指针在分析期间用于获取列数据。根据列的数据类型和是否带符号,选择适当的函数指针,以确保正确地获取数据值。
- 如果传入的 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 ID(cuid)获取列的 CUDesc(列存储单元描述)。它主要完成以下任务:
- 初始化相关变量和上下文。
- 打开 CUDesc 表和其索引。
- 设置用于索引扫描的扫描键。
- 执行索引扫描,找到匹配的 CUDesc 记录。
- 解析 CUDesc 记录,提取其中的信息并存储到 cuDescPtr 结构中。
- 关闭相关的数据库对象,释放资源。
- 返回是否找到匹配的 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)加载相应的删除掩码,以标识是否存在已删除的行。它完成以下任务:
- 初始化相关变量。
- 如果已加载相同的删除掩码,直接返回,无需再次加载。
- 在进行扫描之前,切换到新的内存上下文以管理分配的内存。
- 打开 CUDesc 关系和其索引,以准备执行索引扫描。
- 设置用于索引扫描的扫描键。
- 如果未提供快照,则使用当前活动快照。
- 开始按顺序扫描 CUDesc 关系,查找匹配的记录。
- 如果找到匹配的记录,则提取 CUPointer(列存储单元指针)并相应地处理删除掩码。
- 结束索引扫描并关闭相关的数据库对象。
- 处理异常情况
函数源码如下:(路径: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,表示列存储单元中的行数。
函数内部的逻辑如下:
- 首先,它检查成员变量 m_hasDeadRow 是否为 true,这个变量表示当前列存储中是否包含已删除的行。如果没有已删除的行,那么整个列存储单元就不可能被删除。
- 如果 m_hasDeadRow 为 true,则调用 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
}