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

列存储(CStore)(五)

  • 概述
  • CStore::CStoreScan 函数
  • CStore::CStoreMinMaxScan 函数
  • CStore::LoadCUDescIfNeed 函数
    • CStore::NeedLoadCUDesc 函数
  • CStore::RoughCheckIfNeed 函数
    • CStore::RoughCheck 函数
  • CStore::RefreshCursor 函数
  • CStore::InitRoughCheckEnv 函数
  • CStore::BindingFp 函数
    • CStore::FillColMinMax 函数
  • CStore::InitFillVecEnv 函数
  • CStore::LoadCudescMinus 函数
  • CStore::GetCUXmin 函数
    • CStoreGetfstColIdx 函数

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

概述

  本章我们继续在【 OpenGauss源码学习 —— 列存储(CStore)(三)】和【 OpenGauss源码学习 —— 列存储(CStore)(四)】的基础上进行进一步学习,本文将主要介绍 CStore 类中的部分私有成员函数。

CStore::CStoreScan 函数

  CStore::CStoreScan 函数实现了 CStore 表的扫描操作,主要包括以下步骤:首先加载 CUDesc 信息,然后进行粗略检查排除不满足条件的 CU。接着,根据扫描状态检查是否有匹配的 CU,如果有,则填充 VectorBatch 以存储扫描结果。在处理完当前 CUDesc 之后,刷新游标,标记已处理的行数无效的行数,以便下次扫描时跳过这些行。最后,在适当的情况下进行 CU 列表的预取操作提前加载部分数据到内存,以优化后续的访问效率。这一系列步骤保证了对 CStore 表的高效扫描查询CStoreScan 源码如下:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

// CStoreScan
// 扫描列存储表并填充vecBatchOut
void CStore::CStoreScan(_in_ CStoreScanState* state, _out_ VectorBatch* vecBatchOut)
{
    // 步骤1:持有的CUDesc的数量为max_loaded_cudesc
    // 如果一次性加载所有CUDesc,内存将不足。
    // 因此,我们为max_loaded_cudesc加载CUDesc一次
    CSTORESCAN_TRACE_START(LOAD_CU_DESC);  // 开始加载CUDesc
    LoadCUDescIfNeed();  // 如果需要,加载CUDesc
    CSTORESCAN_TRACE_END(LOAD_CU_DESC);  // 结束加载CUDesc

    // 步骤2:如果需要,进行粗略检查
    // 通过CU的最小/最大值来消除CU。
    CSTORESCAN_TRACE_START(MIN_MAX_CHECK);  // 开始进行粗略检查
    RoughCheckIfNeed(state);  // 如果需要,进行粗略检查
    CSTORESCAN_TRACE_END(MIN_MAX_CHECK);  // 结束粗略检查

    // 步骤3:有命中的CU
    // 我们不会填充矢量,因为没有命中的CU
    ADIO_RUN()
    {
        if (unlikely(m_cursor == m_NumCUDescIdx)) {
            return;
        }
    }
    ADIO_ELSE()
    {
        if (unlikely(m_NumLoadCUDesc == 0)) {
            return;
        }
    }
    ADIO_END();

    // 步骤4:填充VecBatch
    CSTORESCAN_TRACE_START(FILL_BATCH);  // 开始填充VectorBatch
    int deadRows = FillVecBatch(vecBatchOut);  // 填充VectorBatch,获取无效行数
    CSTORESCAN_TRACE_END(FILL_BATCH);  // 结束填充VectorBatch

    // 步骤5:刷新游标
    RefreshCursor(vecBatchOut->m_rows, deadRows);  // 刷新游标,更新已处理的行数和无效的行数

    // 步骤6:如果需要,进行预取
    ADIO_RUN()
    {
        CSTORESCAN_TRACE_START(PREFETCH_CU_LIST);  // 开始预取CU列表
        CUListPrefetch();  // 如果需要,进行CU列表的预取操作
        CSTORESCAN_TRACE_END(PREFETCH_CU_LIST);  // 结束预取CU列表
    }
    ADIO_END();
}

  其中,CStore::CStoreScan 函数的更多详细信息在【 OpenGauss源码学习 —— 列存储(CopyTo)】一文中进行了详细介绍。

CStore::CStoreMinMaxScan 函数

  CStore::CStoreMinMaxScan 函数实现了CStore 表的扫描操作,填充输出的 VectorBatch 数据结构。首先,根据最大加载的 CUDesc 数量加载 CUDesc 信息,然后进行粗略检查以排除不符合条件的 CU。接着,检查是否扫描结束,如果是则直接返回。随后,对每列的 CUDesc 进行遍历,根据是否存在无效行以及是否有 min/max 值的情况,选择性地填充 min/max 值或者全部数据。根据填充情况,修复 VectorBatch 的行数和进行必要的填充值处理。最后,刷新游标并进行预取操作,完成整个扫描过程

/*
 * @Describe: 仅在没有无效行的情况下读取cudesc以获取min/max值
 * 绕过特殊SQL案例的优化,例如类似 'select min(col1), max(col2) from t' 的情况
 * @in - state  CStore扫描状态。
 * @out - vecBatchOut 存储数据的结构。
 */
void CStore::CStoreMinMaxScan(_in_ CStoreScanState* state, _out_ VectorBatch* vecBatchOut)
{
    int pos = 0;

    /*
     * 步骤1:持有的CUDesc的数量是max_loaded_cudesc
     * 如果一次性加载所有CUDesc,内存将不足。
     * 因此,我们为max_loaded_cudesc加载CUDesc一次
     */
    CSTORESCAN_TRACE_START(LOAD_CU_DESC);  // 开始加载CUDesc
    LoadCUDescIfNeed();  // 如果需要,加载CUDesc
    CSTORESCAN_TRACE_END(LOAD_CU_DESC);  // 结束加载CUDesc

    CSTORESCAN_TRACE_START(MIN_MAX_CHECK);  // 开始进行粗略检查
    RoughCheckIfNeed(state);  // 如果需要,进行粗略检查
    CSTORESCAN_TRACE_END(MIN_MAX_CHECK);  // 结束粗略检查

    /* 步骤2:是否扫描结束? */
    if (IsEndScan())
        return;

    /*
     * 步骤3:如果cudesc有min/max,则填充min/max,否则填充所有数据。
     * case1. 所有列的cudesc都有min/max且没有无效行
     * case2. 有无效行
     * case3. 现在有些列根本没有min/max。而有些列有。
     *        例如,numeric列。
     *        描述:未来计划-在加载数据时为numeric列保存min/max
     */
    int idx = m_CUDescIdx[m_cursor];
    int maxVecRows = 0;
    bool needFixRows = false, onlyFillMinMax = true;
    int deadRows = 0;

    CSTORESCAN_TRACE_START(FILL_BATCH);  // 开始填充VectorBatch
    for (int i = 0; i < m_colNum; ++i) {
        int colIdx = m_colId[i];

        ScalarVector* vec = vecBatchOut->m_arr + colIdx;
        CUDesc* cuDescPtr = m_CUDescInfo[i]->cuDescArray + idx;
        GetCUDeleteMaskIfNeed(cuDescPtr->cu_id, m_snapshot);

        Assert(0 == pos);
        if (!m_hasDeadRow && !cuDescPtr->IsNoMinMaxCU() && this->m_fillMinMaxFunc[i]) {
            (this->*m_fillMinMaxFunc[i])(cuDescPtr, vec, pos);
        } else {
            int funIdx = m_hasDeadRow ? 1 : 0;
            deadRows = (this->*m_colFillFunArrary[i].colFillFun[funIdx])(i, cuDescPtr, vec);
            if (vec->m_rows > 1)
                onlyFillMinMax = false;
        }

        /* 如果存在不一致的行,则需要修复。并标记最大的行数。 */
        if (vec->m_rows != maxVecRows) {
            if (0 != maxVecRows)
                needFixRows = true;

            if (vec->m_rows > maxVecRows)
                maxVecRows = vec->m_rows;
        }
    }
    CSTORESCAN_TRACE_END(FILL_BATCH);  // 结束填充VectorBatch

    /*
     * 步骤4:修复VecBatch的m_rows并为某些列添加填充值,如果需要。
     * 因为某些列只填充min/max,而某些列填充某些特殊情况下的批量数据
     */
    if (needFixRows) {
        for (int i = 0; i < m_colNum; ++i) {
            int colIdx = m_colId[i];
            ScalarVector* vec = vecBatchOut->m_arr + colIdx;
            if (vec->m_rows != maxVecRows) {
                for (int k = vec->m_rows; k < maxVecRows; ++k) {
                    /* 修复行,将此值设置为不影响min/max结果的null。 */
                    vec->SetNull(k);
                }
            }
            vec->m_rows = maxVecRows;
        }
    }

    vecBatchOut->m_rows = maxVecRows;

    /* 步骤5:如果只填充min/max,则刷新游标 */
    if (onlyFillMinMax) {
        IncLoadCuDescIdx(m_cursor);
        m_rowCursorInCU = 0;
        if (likely(m_scanPosInCU != NULL)) {
            Assert(m_colNum > 0);
            errno_t rc = memset_s(m_scanPosInCU, sizeof(int) * m_colNum, 0, sizeof(int) * m_colNum);
            securec_check(rc, "", "");
        }
    } else {
        RefreshCursor(vecBatchOut->m_rows, deadRows);
    }

    pos = vecBatchOut->m_rows;
    Assert(pos <= BatchMaxSize);
}

  这里详细解释一下如下代码逻辑:

/* 如果存在不一致的行,则需要修复。并标记最大的行数。 */
if (vec->m_rows != maxVecRows) {
    if (0 != maxVecRows)
        needFixRows = true;

	if (vec->m_rows > maxVecRows)
		maxVecRows = vec->m_rows;
}

  这段代码的目的是确保不同列的 vecScalarVector 对象)中的行数一致。具体情况可能如下:
  假设有一个表,其中的每一列都有不同的填充逻辑,有些列可能只填充了部分行,而有些列可能填充了所有行。在执行 CStoreMinMaxScan 函数时,通过循环遍历每一列的 vec,检查它们的行数是否一致

  1. 如果某个行数不等于 maxVecRows,说明该列的填充逻辑导致它的行数与其他列不一致。
  2. 如果 maxVecRows,表示这是第一次检查,将 needFixRows 设为 true
  3. 如果某列的行数大于 maxVecRows,更新 maxVecRows该列的行数,以保证后续的行数检查使用的是最大值

举例说明:
  假设有三列 A、BC,它们的 vec 分别为:

列 A:[1, 2, 3]
列 B:[4, 5]
列 C:[6, 7, 8, 9]

  在第一次检查时,maxVecRows 为零,needFixRows 设为 true。在接下来的检查中,maxVecRows 会被更新为 4,因为列 C 的行数最多。在最后的修正阶段,列 A 和列 B 会被填充为 [1, 2, 3, null][4, 5, null, null],以保证它们的行数与列 C 一致。这样,vecBatchOut 中的每一列都具有相同的行数,确保后续的处理是一致的


  CStore::CStoreScanCStore::CStoreMinMaxScan 函数是用于扫描 CStore 表的两个不同的方法。

CStore::CStoreScan:

  1. 作用和功能: 该函数用于普通的 CStore 表扫描,它通过加载 CUDesc 信息、进行粗略检查、判断是否扫描结束,以及填充 VectorBatch 数据结构等步骤完成扫描操作。它适用于一般的表扫描场景填充所有的数据

CStore::CStoreMinMaxScan:

  1. 作用和功能: 与普通扫描不同,该函数用于进行特殊的 CStore 表扫描,主要用于执行类似 'select min(col1), max(col2) from t' 这样的 SQL 查询。它通过加载 CUDesc 信息、进行粗略检查、检查是否扫描结束,然后根据每列的具体情况,选择性地填充 min/max 值或者全部数据。这个函数的优化点在于根据需要选择性地填充数据,而不是一次性填充所有数据。

CStore::LoadCUDescIfNeed 函数

  CStore::LoadCUDescIfNeed 函数的主要作用是在需要的情况下加载 CUDesc(Column Unit Descriptor)。若需要加载,则遍历所有访问的列,调用 LoadCUDesc 函数逐列加载 CUDesc。函数根据是否启用 ADIOAdaptive I/O)功能,以及是否达到预取阈值等条件,决定是否继续加载 CUDesc。加载完成后,进行一系列的检查,包括检查 CUDesc 控制块的一致性检查每列的第一个和最后一个 CUDesc,以确保加载的 CUDesc 信息正确。加载完成后,根据访问列的类型,确定使用哪个列的 CUDesc 信息,然后进行相应的内存操作状态更新。最终,根据 ADIO 是否启用,确定是否需要进行粗略检查,并更新加载的 CUDesc 信息的长度。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

/*
 * @Description: 如果需要加载 CUDesc 且当前未加载足够数量的 CUDesc,则加载 CUDesc。
 *                遍历所有访问的列,调用 LoadCUDesc 函数加载 CUDesc。
 *                对于 ADIO 功能,如果发现未达到预取阈值,且有足够的 CUDesc 插槽,则继续加载。
 *                加载完成后,设置 lastLoadNum 以备份值。
 *                如果只包含系统列或常量列,则使用第一列的 CUDesc。
 * @IN state: CStore Scan State,包含了 CStore 扫描的状态信息。
 * @OUT vecBatchOut: 存储数据的 VectorBatch 结构。
 */
void CStore::LoadCUDescIfNeed()
{
    uint32 last_load_num = 0;  // 上次加载的 CUDesc 数量
    int32 cudesc_idx = 0;     // cudesc_idx 在 buffer io 时设置为0,在 adio 时设置为 m_NumCUDescIdx

    // 如果不需要加载 CUDesc,则直接返回
    if (!NeedLoadCUDesc(cudesc_idx)) {
        return;
    }

    m_NumLoadCUDesc = 0;  // 初始化加载的 CUDesc 数量为0

    Assert(m_perScanMemCnxt);  // 断言扫描内存上下文不为空
    // 当批量 CU 已扫描和处理时,重置内存上下文
    MemoryContextReset(m_perScanMemCnxt);
#ifdef MEMORY_CONTEXT_CHECKING
    MemoryContextCheck(m_perScanMemCnxt->parent, m_perScanMemCnxt->parent->session_id > 0);
#endif

    // 加载所有访问的列的 CUDesc
    if (m_colNum > 0) {
        last_load_num = m_CUDescInfo[0]->curLoadNum;  // 记录加载的列的 CUDesc 数量
    }

    do {
        bool found = false;  // 是否找到 CUDesc 的标志
        for (int i = 0; i < m_colNum; ++i) {
            Assert(m_colId[i] >= 0);
            // 如果启用了 ADIO,则加载一个 CU 以计算预取数量
            found = LoadCUDesc(m_colId[i], m_CUDescInfo[i], g_instance.attr.attr_storage.enable_adio_function, m_snapshot);
        }

        if (likely(m_colNum > 1 && m_CUDescInfo[0]->curLoadNum > 0)) {
            CheckConsistenceOfCUDescCtl();
            /* 检查所有列的第一个 CUDesc */
            CheckConsistenceOfCUDesc(0);
            /* 检查所有列的最后一个 CUDesc */
            if (m_CUDescInfo[0]->curLoadNum > 1) {
                CheckConsistenceOfCUDesc(m_CUDescInfo[0]->curLoadNum - 1);
            }
        }

        if (m_colNum > 0) {
            // 更新加载的 CUDesc 的索引
            for (int j = (int)m_CUDescInfo[0]->lastLoadNum; j != (int)m_CUDescInfo[0]->curLoadNum;
                 IncLoadCuDescIdx(j)) {
                m_CUDescIdx[cudesc_idx] = j;
                IncLoadCuDescIdx(cudesc_idx);
            }
            // 计算加载的 CUDesc 数量
            m_NumLoadCUDesc += LoadCudescMinus(m_CUDescInfo[0]->lastLoadNum, m_CUDescInfo[0]->curLoadNum);
        }

        ADIO_RUN()
        {
            // 如果找到 CUDesc,检查预取数量并决定是否需要加载更多的 CUDesc
            if (found && m_prefetch_quantity < m_prefetch_threshold &&
                HasEnoughCuDescSlot(last_load_num, m_CUDescInfo[0]->curLoadNum)) {
                continue;
            }
            // 加载完成,设置 lastLoadNum 为备份值
            for (int i = 0; i < m_colNum; ++i) {
                m_CUDescInfo[i]->lastLoadNum = last_load_num;
            }
            if (m_colNum > 0) {
                // 设置最小预取数量,因为我们需要预取窗口来控制是否需要预取
                t_thrd.cstore_cxt.cstore_prefetch_count = Max(m_NumLoadCUDesc, CSTORE_MIN_PREFETCH_COUNT);
                ereport(DEBUG1,
                        (errmodule(MOD_ADIO),
                         errmsg("LoadCUDesc: columns(%d), count(%d), quantity(%d)",
                                m_colNum,
                                m_NumLoadCUDesc,
                                m_prefetch_quantity)));
            }
            break;
        }
        ADIO_ELSE()
        {
            break;
        }
        ADIO_END();
    } while (1);

    // 系统列和常量列
    if (m_colNum > 0 && m_sysColNum != 0) {
        // 访问正常列和系统列,使用正常列的 CUDesc
        m_virtualCUDescInfo = m_CUDescInfo[0];
    } else if (OnlySysOrConstCol()) {
        // 只有系统列或常量列,使用第一列的 CUDesc
        Assert(m_virtualCUDescInfo);
        LoadCUDesc(m_firstColIdx, m_virtualCUDescInfo, false, m_snapshot);

        for (int j = (int)m_virtualCUDescInfo->lastLoadNum; j != (int)m_virtualCUDescInfo->curLoadNum;
             IncLoadCuDescIdx(j)) {
            m_CUDescIdx[cudesc_idx] = j;
            IncLoadCuDescIdx(cudesc_idx);
        }
        m_NumLoadCUDesc = LoadCudescMinus(m_virtualCUDescInfo->lastLoadNum, m_virtualCUDescInfo->curLoadNum);
        // ADIO 使用它,但无需添加 ADIO_RUN(),因为对于缓冲 IO 它没有用处
        t_thrd.cstore_cxt.cstore_prefetch_count = m_NumLoadCUDesc;
    }

    // 加载新的 CU 需要进行粗略检查
    m_needRCheck = true;

    // 在进行粗略检查之前,m_NumCUDescIdx 是加载的 CUDesc 信息的长度
    BFIO_RUN()
    {
        m_NumCUDescIdx = m_NumLoadCUDesc;
    }
    BFIO_END();
    return;
}

CStore::NeedLoadCUDesc 函数

  CStore::NeedLoadCUDesc 函数用于检查是否满足加载 CUDesc 的条件,判断是否需要加载 CUDesc。在使用 ADIOAdaptive I/O)功能的情况下,首先检查是否已加载完所有的 CUDescm_load_finish),然后检查是否超过了预取数量的一半。如果是,则设置 need_loadtrue,将 cudesc_idx 设置为当前已加载 CUDesc索引,并重置预取数量。在没有使用 ADIO 功能的情况下,检查当前游标是否超过了已加载的 CUDesc 的数量,如果是,则设置 need_loadtrue,将 cudesc_idx 设置为 0,同时重置游标。最终,函数返回 need_load 的值

/*
 * @Description: 检查是否满足加载 CUDesc 的条件,并设置加载 CUDesc 的数组索引等信息
 * @Param[IN] cudesc_idx: 加载 CUDesc 的数组索引(输出参数)
 * @Return: true--需要加载 CUDesc, false-- 不需要加载
 * @See also:
 */
bool CStore::NeedLoadCUDesc(int32& cudesc_idx)
{
    bool need_load = false;

    ADIO_RUN()
    {
        // 检查加载条件,首先确保没有加载完成,其次不超过预取数量的一半
        if (!m_load_finish && (m_cursor == m_NumCUDescIdx || LoadCudescMinus(m_cursor, m_NumCUDescIdx) <=
                               t_thrd.cstore_cxt.cstore_prefetch_count / 2)) {
            need_load = true;
            m_prefetch_quantity = 0;
            cudesc_idx = m_NumCUDescIdx;
        }
    }
    ADIO_ELSE()
    {
        // 在没有启用 ADIO 功能的情况下,检查当前游标是否超过已加载 CUDesc 的数量
        if (m_cursor >= m_NumLoadCUDesc) {
            need_load = true;
            m_cursor = 0;
            cudesc_idx = 0;
        }
    }
    ADIO_END();

    return need_load;
}

CStore::RoughCheckIfNeed 函数

  CStore::RoughCheckIfNeed 函数是 CStore 扫描过程中的一个步骤,用于执行 “Rough Check”,即粗略检查,以判断当前的扫描条件是否能够命中列存储中的压缩单元(CU)。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

/*
 * @Description: 如果需要进行粗略检查,则执行粗略检查操作。
 * @IN state: CStore扫描状态
 */
void CStore::RoughCheckIfNeed(_in_ CStoreScanState* state)
{
    int nkeys = state->csss_NumScanKeys;  // 获取扫描条件的数量
    CStoreScanKey scanKey = state->csss_ScanKeys;  // 获取扫描条件的数组
    PlanState* planstate = (PlanState*)state;  // 获取计划节点的状态
    uint32 curLoadNum;
    uint32 lastLoadNum;

    // 当m_needRCheck为false时,表示已经执行过粗略检查,无需重复执行
    // 当m_colNum为0时,表示没有普通列,也无需执行粗略检查
    if (likely(!m_needRCheck)) {
        return;
    }

    // 当没有扫描条件或者没有普通列时,也无需执行粗略检查
    if (likely(nkeys == 0 || scanKey == NULL || m_colNum == 0)) {
        // 当没有WHERE条件时,仍需设置m_lastNumCUDescIdx和m_NumCUDescIdx,以备预取
        ADIO_RUN()
        {
            m_NumCUDescIdx = (m_NumCUDescIdx + m_NumLoadCUDesc) % u_sess->attr.attr_storage.max_loaded_cudesc;
            m_needRCheck = false;  // 设置标志,表示已完成粗略检查
        }
        ADIO_END();
        return;
    }

    int pos = 0;
    bool hitCU = true;
    int cudesc_idx_tmp = 0;

    ADIO_RUN()
    {
        cudesc_idx_tmp = m_NumCUDescIdx;  // 记录当前的CU索引
        pos = m_NumCUDescIdx;  // 记录粗略检查的起始位置
    }
    ADIO_END();

    lastLoadNum = m_CUDescInfo[0]->lastLoadNum;
    curLoadNum = m_CUDescInfo[0]->curLoadNum;

    // 遍历所有的CU,进行粗略检查
    for (int i = (int)lastLoadNum; i != (int)curLoadNum; IncLoadCuDescIdx(i), IncLoadCuDescIdx(cudesc_idx_tmp)) {
        hitCU = RoughCheck(scanKey, nkeys, i);  // 执行粗略检查操作
        if (hitCU) {
            // 将命中的CU索引记录下来,用于后续的预取
            ADIO_RUN()
            {
                m_CUDescIdx[pos] = m_CUDescIdx[cudesc_idx_tmp];
            }
            ADIO_ELSE()
            {
                m_CUDescIdx[pos] = m_CUDescIdx[i];
            }
            ADIO_END();

            IncLoadCuDescIdx(pos);
        }

        // 统计未命中的CU的行数,用于性能统计
        if (planstate->instrument) {
            RCInfo* rcPtr = &(planstate->instrument->rcInfo);

            if (!hitCU) {
                // 获取未命中的CU的行数,并更新性能统计信息
                int seq = scanKey[0].cs_attno;
                CUDesc* cudesc = &(m_CUDescInfo[seq]->cuDescArray[i]);
                planstate->instrument->nfiltered1 += cudesc->row_count;

                Relation cuDescRel = heap_open(m_relation->rd_rel->relcudescrelid, AccessShareLock);
                Relation idxRel = index_open(cuDescRel->rd_rel->relcudescidx, AccessShareLock);
                ScanKeyData key[2];

                // 使用索引进行查询,获取CU的行数和位图
                ScanKeyInit(&key[0], (AttrNumber)CUDescColIDAttr, BTEqualStrategyNumber,
                            F_INT4EQ, Int32GetDatum(VitrualDelColID));
                ScanKeyInit(&key[1], (AttrNumber)CUDescCUIDAttr, BTEqualStrategyNumber,
                            F_OIDEQ, UInt32GetDatum(cudesc->cu_id));
                SysScanDesc cuDescScan = systable_beginscan_ordered(cuDescRel, idxRel, m_snapshot, 2, key);

                HeapTuple tmpTup = NULL;

                if ((tmpTup = systable_getnext_ordered(cuDescScan, ForwardScanDirection)) != NULL) {
                    bool isNull = false;
                    uint32 deadRowCount = 0;
                    uint32 rowCount = DatumGetUInt32(fastgetattr(tmpTup, CUDescRowCountAttr,
                        cuDescRel->rd_att, &isNull));
                    Datum v = fastgetattr(tmpTup, CUDescCUPointerAttr, cuDescRel->rd_att, &isNull);
                    if (!isNull) {
                        // 解析位图,统计未命中的行数
                        int8* bitmap = (int8*)PG_DETOAST_DATUM(DatumGetPointer(v));
                        unsigned char delBitMap[MaxDelBitmapSize];
                        uint32 nBytes = (rowCount + 7) / 8;
                        errno_t rc = memcpy_s(delBitMap, MaxDelBitmapSize,
                                              VARDATA_ANY(bitmap), VARSIZE_ANY_EXHDR(bitmap));
                        securec_check(rc, "", "");
                        for (uint32 j = 0; j < nBytes; j++) {
                            deadRowCount += NumberOfBit1Set[delBitMap[j]];
                        }
                        // 释放位图的内存
                        if ((Pointer)bitmap != DatumGetPointer(v)) {
                            pfree_ext(bitmap);
                        }
                    }
                    // 更新性能统计信息,减去未命中的行数
                    planstate->instrument->nfiltered1 -= deadRowCount;
                }
                // 结束查询
                systable_endscan_ordered(cuDescScan);
                index_close(idxRel, AccessShareLock);
                heap_close(cuDescRel, AccessShareLock);

                rcPtr->IncNoneCUNum();  // 更新性能统计信息
            } else {
                rcPtr->IncSomeCUNum();  // 更新性能统计信息
            }
            planstate->instrument->needRCInfo = true;
        }
    }

    ADIO_RUN()
    {
        // 更新m_NumCUDescIdx,表示粗略检查的结束位置
        if (pos == m_NumCUDescIdx) {
            m_NumCUDescIdx = (m_NumCUDescIdx + m_NumLoadCUDesc) % u_sess->attr.attr_storage.max_loaded_cudesc;
        } else {
            m_NumCUDescIdx = pos;
        }
    }
    ADIO_ELSE()
    {
        m_NumLoadCUDesc = pos;
    }
    ADIO_END();

    // 设置标志,表示已完成粗略检查
    m_needRCheck = false;
}

  粗略检查是通过比较扫描条件与压缩单元(CU)的元数据信息来判断是否命中的过程。下面举例说明
  假设有一个列存储表,其中包含一个整数列(col1)和一个字符串列(col2),现在有一个查询条件col1 > 100 AND col2 = 'abc'。执行粗略检查时,会比较每个压缩单元CU)的元数据信息,例如检查每个 CU 的最小值和最大值,以确定是否有可能包含满足查询条件的行
  例如,对于整数列 col1,如果某个 CU最大值小于等于100,则可以确定该 CU 不可能包含满足查询条件的行,因此可以跳过对该 CU 的详细扫描。对于字符串列 col2,如果某个 CU 的最小值大于 ‘abc’ 或者最大值小于 ‘abc’,同样可以确定该 CU 不可能包含满足查询条件的行。这样就可以在不详细扫描具体行数据的情况下,快速过滤掉一些不符合条件的 CU,提高查询效率

CStore::RoughCheck 函数

  CStore::RoughCheck 函数的主要作用是根据扫描键scanKey)和CUDesc 信息,判断当前 CUDesc 是否满足给定的扫描条件。例如,对于一个查询条件 col1 > 100 AND col2 = 'abc',该函数将检查每个 CUDesc元数据信息,比较是否满足条件。如果某个 CUDesc最小值大于100,且对应的字符串列最大值等于 ‘abc’,则认为该 CUDesc 符合条件,返回 true。如果某个条件未满足,则返回 false,表示该 CUDesc 未命中。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

/*
 * @Description: 执行CUDesc的粗略检查
 * @Param[IN] cuDescIdx: 加载的CUDesc信息的索引
 * @Param[IN] nkeys: 扫描键的数量
 * @Param[IN] scanKey: CStore扫描键
 * @Return: true--命中, false--未命中
 * @See also:
 */
bool CStore::RoughCheck(CStoreScanKey scanKey, int nkeys, int cuDescIdx)
{
    bool hitCU = true;  // 假设命中CU

    // 遍历所有的扫描键
    for (int j = 0; j < nkeys; j++) {
        int seq = scanKey[j].cs_attno;  // 获取扫描键对应的列序号
        CUDesc* cudesc = &(m_CUDescInfo[seq]->cuDescArray[cuDescIdx]);  // 获取对应列的CUDesc信息
        bool isNullKey = scanKey[j].cs_flags & SK_ISNULL;  // 判断是否为NULL键

        // 如果CUDesc是NULL,但扫描键不是NULL,则继续下一个键
        if ((cudesc->IsNullCU() && !isNullKey) || cudesc->IsNoMinMaxCU())
            continue;

        // 如果扫描键是NULL,判断CUDesc是否包含NULL值,或者CUDesc本身是NULL
        if (isNullKey)
            hitCU = cudesc->CUHasNull() || cudesc->IsNullCU();
        else
            // 否则,调用相应的比较函数(如大于、小于、等于等)进行比较
            hitCU = m_RCFuncs[j](cudesc, scanKey[j].cs_argument);

        // 如果未命中,则跳出循环
        if (!hitCU)
            break;
    }

    return hitCU;  // 返回是否命中CU
}

CStore::RefreshCursor 函数

  该函数主要用于管理在 CStore 扫描过程中,当前所处的CUDesc 以及在该 CUDesc 中的行游标位置。在扫描过程中,每次刷新游标后,都会检查当前 CUDesc 是否已经扫描完毕,如果是,则更新游标到下一个 CUDesc,并将行游标重置为 0。这样可以有效地管理扫描的状态,确保在 CStore 表中正确地进行行扫描。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

/*
 * @Description: 刷新游标,更新当前扫描位置
 * @Param[IN] row: 当前批次的行数
 * @Param[IN] deadRows: 当前批次中的死行数
 */
void CStore::RefreshCursor(int row, int deadRows)
{
    int cuRowCount = 0;
    int idx = m_CUDescIdx[m_cursor];  // 获取当前游标指向的CUDesc的索引

    // 判断是否有正常列的CUDesc信息
    if (likely(m_CUDescInfo != NULL)) {
        cuRowCount = m_CUDescInfo[0]->cuDescArray[idx].row_count;  // 获取当前CUDesc的行数信息
    } else {
        Assert(m_virtualCUDescInfo);
        cuRowCount = m_virtualCUDescInfo->cuDescArray[idx].row_count;  // 获取虚拟CUDesc的行数信息
    }

    // 更新在当前CUDesc中的行游标
    m_rowCursorInCU = m_rowCursorInCU + row + deadRows;

    // 断言行游标不超过CUDesc的行数
    Assert(m_rowCursorInCU <= cuRowCount);

    // 如果行游标达到CUDesc的行数,表示当前CUDesc已经扫描完毕,更新游标到下一个CUDesc
    if (unlikely(m_rowCursorInCU == cuRowCount)) {
        IncLoadCuDescIdx(m_cursor);  // 更新游标到下一个CUDesc
        m_rowCursorInCU = 0;  // 重置行游标为0

        // 如果有正常的扫描位置信息,则将其重置为0
        if (likely(m_scanPosInCU != NULL)) {
            Assert(m_colNum > 0);
            errno_t rc = memset_s(m_scanPosInCU, sizeof(int) * m_colNum, 0, sizeof(int) * m_colNum);
            securec_check(rc, "", "");
        }
    }
}

CStore::InitRoughCheckEnv 函数

  该函数主要用于CStore 扫描过程中初始化粗略检查所需的环境。首先,通过 AutoContextSwitch将当前上下文切换为 m_scanMemContext ,确保内存管理的正确性。然后,根据扫描状态中的扫描键信息获取每个扫描键对应的粗略检查函数,并存储在 m_RCFuncs 数组中。这样,在后续的粗略检查过程中,可以直接调用相应的函数进行检查。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

/*
 * @Description: 初始化粗略检查环境
 * @Param[IN] state: CStore扫描状态
 */
void CStore::InitRoughCheckEnv(CStoreScanState* state)
{
    // 使用m_scanMemContext,该上下文直到析构函数被调用才会释放。
    AutoContextSwitch newMemCnxt(m_scanMemContext);

    // 初始化粗略检查函数
    int nkeys = state->csss_NumScanKeys;
    if (nkeys > 0) {
        CStoreScanKey scanKey = state->csss_ScanKeys;
        Relation rel = state->ss_currentRelation;
        Form_pg_attribute* attrs = rel->rd_att->attrs;

        // 为每个扫描键获取对应的粗略检查函数
        m_RCFuncs = (RoughCheckFunc*)palloc(sizeof(RoughCheckFunc) * nkeys);
        for (int i = 0; i < nkeys; i++) {
            int colIdx = m_colId[scanKey[i].cs_attno];
            // 获取粗略检查函数,根据属性类型、策略和排序规则
            m_RCFuncs[i] = GetRoughCheckFunc(attrs[colIdx]->atttypid, scanKey[i].cs_strategy, scanKey[i].cs_collation);
        }
    }
}

CStore::BindingFp 函数

  CStore::BindingFp 函数主要用于为 CStore 扫描过程中的函数指针赋值。首先,它检查是否存在最大/最小值投影列,如果是,则为每个列根据其数据类型设置相应的最大/最小值填充函数。然后,它为每个列初始化 LoadCUDescCtl,并根据数据类型设置相应的填充函数。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

/*
 * @Description: 根据不同的数据类型为函数指针赋值
 * @in - state: CStore扫描状态
 */
void CStore::BindingFp(CStoreScanState* state)
{
    int i = 0;
    Relation rel = state->ss_currentRelation;
    ProjectionInfo* proj = state->ps.ps_ProjInfo;

    // 如果存在最大/最小值投影列
    if (proj->pi_maxOrmin) {
        Assert(list_length(proj->pi_maxOrmin) == list_length(proj->pi_acessedVarNumbers));
        m_scanFunc = &CStore::CStoreMinMaxScan;
        m_fillMinMaxFunc = (fillMinMaxFuncPtr*)palloc0(sizeof(fillMinMaxFuncPtr) * m_colNum);

        // 为每个列根据数据类型设置最大/最小值填充函数
        for (i = 0; i < m_colNum; ++i) {
            switch (rel->rd_att->attrs[m_colId[i]]->atttypid) {
                case CHAROID:
                case INT2OID:
                case INT4OID:
                case INT8OID:
                case OIDOID:
                case DATEOID:
                case TIMEOID:
                case TIMESTAMPOID: {
                    m_fillMinMaxFunc[i] = &CStore::FillColMinMax;
                    break;
                }
                default: {
                    m_fillMinMaxFunc[i] = NULL;
                    break;
                }
            }
        }
    }

    // 为每个列初始化LoadCUDescCtl,并根据数据类型设置相应的填充函数
    for (i = 0; i < m_colNum; ++i) {
        m_CUDescInfo[i] = New(CurrentMemoryContext) LoadCUDescCtl(m_startCUID);
        if (m_colId[i] > rel->rd_att->natts - 1 || m_colId[i] < 0) {
            continue;
        }
        switch (rel->rd_att->attrs[m_colId[i]]->attlen) {
            case sizeof(char):
                InitFillColFunction(i, (int)sizeof(char));
                break;
            case sizeof(int16):
                InitFillColFunction(i, (int)sizeof(int16));
                break;
            case sizeof(int32):
                InitFillColFunction(i, (int)sizeof(int32));
                break;
            case sizeof(Datum):
                InitFillColFunction(i, (int)sizeof(Datum));
                break;
            case 12:
                InitFillColFunction(i, 12);
                break;
            case 16:
                InitFillColFunction(i, 16);
                break;
            case -1:
                InitFillColFunction(i, -1);
                break;
            case -2:
                InitFillColFunction(i, -2);
                break;
            default:
                ereport(ERROR,
                        (errcode(ERRCODE_DATATYPE_MISMATCH),
                         (errmsg("unsupported data type length %d of column \"%s\" of relation \"%s\" ",
                                 (int)rel->rd_att->attrs[m_colId[i]]->attlen,
                                 NameStr(rel->rd_att->attrs[m_colId[i]]->attname),
                                 RelationGetRelationName(rel)))));
                break;
        }
    }
}

CStore::FillColMinMax 函数

  CStore::FillColMinMax 函数主要用于处理列存储中的元数据信息,通过提供最小值最大值有助于查询优化器生成更有效的执行计划。函数中的 cuDescPtr列单元描述符,包含有关列单元的信息,而 vecScalarVector 结构,用于存储列数据。函数通过检查是否是空列单元来确定是否设置为 NULL,并将最小值最大值填充到 ScalarVector 中。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

/*
 * @Describe: 从 CU(Column Unit)描述符中获取最大值和最小值。
 *
 * @in - cuDescPtr: CU信息的指针
 * @in - pos: 当前行号
 * @out - vec: ScalarVector结构,用于存储列数据
 */
void CStore::FillColMinMax(CUDesc* cuDescPtr, ScalarVector* vec, int pos)
{
    // 如果不是空 CU,则从 CU 描述符中获取最小值和最大值,并填充到 ScalarVector 中
    if (!cuDescPtr->IsNullCU()) {
        vec->m_vals[pos] = *(ScalarValue*)cuDescPtr->cu_min;
        vec->m_vals[pos + 1] = *(ScalarValue*)cuDescPtr->cu_max;
    } else {
        // 如果是空 CU,则设置相应位置的值为 NULL
        vec->SetNull(pos);
        vec->SetNull(pos + 1);
    }

    // 更新 ScalarVector 的行数信息
    vec->m_rows = pos + 2;
}

CStore::InitFillVecEnv 函数

  该函数用于初始化 CStore 扫描环境,包括为列的访问延迟读取等设置相应的参数。具体功能如下:

  1. 在函数内部使用 AutoContextSwitch 对象,将内存上下文切换为 m_scanMemContext,确保分配的内存在扫描结束前一直有效
  2. 通过访问 ProjectionInfo 对象,获取投影信息,包括要访问的列延迟读取的列系统列等。
  3. 根据投影信息,初始化以下参数:
  • m_colNum需要访问的列的数量
  • m_colId需要访问的列的编号数组
  • m_lateRead标识是否需要延迟读取的列的数组。
  • m_scanPosInCU记录每列在当前 CU 中的扫描位置的数组。
  • m_CUDescInfoLoadCUDescCtl 对象数组,用于管理每列的 CU 描述符
  • m_colFillFunArrary列填充函数数组,用于根据不同的数据类型选择相应的填充函数
  • m_fillVectorByTids填充向量的函数数组,用于通过 TIDs 填充向量。
  • m_fillVectorLateRead延迟读取填充向量函数数组
  1. 调用 BindingFp 函数,为不同的数据类型的列设置相应的填充函数
  2. 如果存在系统列,初始化以下参数:
  • m_sysColNum系统列的数量
  • m_sysColId系统列的编号数组
  1. 设置 m_onlyConstCol 标志,表示是否仅访问系统列或常量列
  2. 如果仅访问系统列或常量列,创建虚拟LoadCUDescCtl 对象。

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

/*
 * @Description: 初始化CStore扫描环境,包括为列的访问、延迟读取等设置相应的参数。
 * @in - state: CStore扫描状态。
 */
void CStore::InitFillVecEnv(CStoreScanState* state)
{
    // 使用AutoContextSwitch对象,将内存上下文切换为m_scanMemContext,确保分配的内存在扫描结束前一直有效。
    AutoContextSwitch newMemCnxt(m_scanMemContext);

    // 获取投影信息,包括要访问的列、延迟读取的列、系统列等。
    ProjectionInfo* proj = state->ps.ps_ProjInfo;

    // 如果存在需要访问的列
    if (proj->pi_acessedVarNumbers != NIL) {
        List* pColList = proj->pi_acessedVarNumbers;

        // 初始化需要访问的列的相关参数
        m_colNum = list_length(pColList);
        m_colId = (int*)palloc(sizeof(int) * m_colNum);
        m_lateRead = (bool*)palloc0(sizeof(bool) * m_colNum);
        int i = 0;
        ListCell* cell = NULL;

        // 初始化需要访问的列的编号数组
        foreach (cell, pColList) {
            // m_colIdx[] start from zero
            Assert(lfirst_int(cell) > 0);
            int colId = lfirst_int(cell) - 1;

            // 检查列是否存在
            if (colId >= m_relation->rd_att->natts) {
                ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN),
                                errmsg("column %d does not exist", colId)));
            }

            // 检查列是否被删除
            if (m_relation->rd_att->attrs[colId]->attisdropped) {
                ereport(ERROR, (errcode(ERRCODE_UNDEFINED_COLUMN),
                                errmsg("column %s does not exist",
                                       NameStr(m_relation->rd_att->attrs[colId]->attname))));
            }

            m_colId[i] = colId;
            m_lateRead[i] = false;
            i++;
        }

        // 初始化延迟读取的列的标志
        foreach (cell, proj->pi_lateAceessVarNumbers) {
            int colId = lfirst_int(cell) - 1;
            for (i = 0; i < m_colNum; ++i) {
                if (colId == m_colId[i]) {
                    m_lateRead[i] = true;
                    break;
                }
            }
        }

        // 初始化每列在当前CU中的扫描位置的数组
        m_scanPosInCU = (int*)palloc0(sizeof(int) * m_colNum);

        // 初始化每列的CU描述符管理对象数组
        m_CUDescInfo = (LoadCUDescCtl**)palloc(sizeof(LoadCUDescCtl*) * m_colNum);

        // 初始化列填充函数数组
        m_colFillFunArrary = (colFillArray*)palloc(sizeof(colFillArray) * m_colNum);

        // 初始化填充向量的函数数组
        m_fillVectorByTids = (FillVectorByTidsFun*)palloc(sizeof(FillVectorByTidsFun) * m_colNum);

        // 初始化延迟读取的填充向量的函数数组
        m_fillVectorLateRead = (FillVectorLateReadFun*)palloc(sizeof(FillVectorLateReadFun) * m_colNum);

        // 调用BindingFp函数,为不同的数据类型的列设置相应的填充函数。
        BindingFp(state);
    }

    // 如果存在系统列
    if (proj->pi_sysAttrList != NIL) {
        ListCell* cell = NULL;
        List* pSysList = proj->pi_sysAttrList;

        // 初始化系统列的相关参数
        m_sysColNum = list_length(pSysList);
        m_sysColId = (int*)palloc(sizeof(int) * m_sysColNum);
        int i = 0;

        // 初始化系统列的编号数组
        foreach (cell, pSysList) {
            m_sysColId[i++] = lfirst_int(cell);
        }
    }

    // 设置m_onlyConstCol标志,表示是否仅访问系统列或常量列。
    m_onlyConstCol = proj->pi_const;

    // 检查标志,如果仅访问系统列或常量列,确保其他参数为0。
    #ifdef USE_ASSERT_CHECKING
    if (m_onlyConstCol) {
        Assert(m_colNum == 0 && m_sysColNum == 0);
    }
    #endif

    // 如果仅访问系统列或常量列,创建虚拟的LoadCUDescCtl对象。
    if (OnlySysOrConstCol()) {
        m_virtualCUDescInfo = New(CurrentMemoryContext) LoadCUDescCtl(m_startCUID);
    }
}

CStore::LoadCudescMinus 函数

  CStore::LoadCudescMinus 函数用于计算已加载的 CUDesc 的数量,接收 CUDesc 数组的起始结束索引位置,并考虑了数组索引环绕的情况。在结束位置大于等于起始位置时,直接计算两者的差值;而在结束位置小于起始位置时,考虑数组索引环绕的情况,通过绕过起始位置结束位置的数量来计算已加载的 CUDesc 的总数。函数返回已加载的 CUDesc 的数量。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

/*
 * @Description: 计算已加载的CUDesc的数量
 * @Param[IN] end: 数组索引的结束位置
 * @Param[IN] start: 数组索引的起始位置
 * @Return: 已加载的CUDesc的数量
 * @See also:
 */
int CStore::LoadCudescMinus(int start, int end) const
{
    // 如果结束位置大于等于起始位置,直接计算差值
    if (end >= start) {
        return end - start;
    }

    // 否则,考虑数组环绕的情况,计算绕过起始位置到结束位置的数量
    return u_sess->attr.attr_storage.max_loaded_cudesc - start + end;
}

CStore::GetCUXmin 函数

  CStore::GetCUXmin 函数的主要目的是从给定的 CU ID 中获取对应 CUxmin,即最小事务ID。函数通过访问列的 cudec 记录,使用列 0cudec 记录中的 xmin 作为 CUxmin。如果未找到相应的 CU 描述符,函数会抛出致命错误。函数源码如下所示:(路径:src/gausskernel/storage/cstore/cstore_am.cpp

/*
 * @Description: 根据 CU(列单元)的 ID 获取对应的 CU 的 xmin(最小事务ID)。
 * @IN cuid: CU 的 ID
 * @Return: CU 的 xmin
 */
TransactionId CStore::GetCUXmin(uint32 cuid)
{
    // 获取第一个未丢弃的列的索引
    int colid = CStoreGetfstColIdx(m_relation);

    // 使用列0的 cudec 记录的 xmin 作为 cu xmin
    CUDesc cudesc;
    // 通过给定的 CU ID 获取对应的 CU 描述符
    bool found = this->GetCUDesc(colid, cuid, &cudesc, m_snapshot);
    if (!found) {
        // 如果未找到 CU 描述符,抛出错误
        Assert(false);
        ereport(FATAL,
                (errmsg("未找到压缩单元描述符(表 \"%s\",列 \"%s\",cuid %u)",
                        RelationGetRelationName(m_relation),
                        NameStr(m_relation->rd_att->attrs[colid]->attname),
                        cuid)));
    }

    // 返回获取到的 CU 的 xmin
    return cudesc.xmin;
}

CStoreGetfstColIdx 函数

  CStoreGetfstColIdx 函数的目的获取在给定的关系(Relation)中第一个没有被删除dropped的列的索引。函数遍历关系的所有属性,找到第一个没有被标记为已删除的属性,然后返回其索引。函数源码如下所示:(路径: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: 如果在给定的关系中找不到没有被删除的属性,则默认返回 0
 */
int CStoreGetfstColIdx(Relation rel)
{
    // 遍历关系中的所有属性
    for (int i = 0; i < rel->rd_att->natts; i++) {
        // 判断属性是否已删除。如果属性已删除,attisdropped 将为真(true)。
        if (!rel->rd_att->attrs[i]->attisdropped)
            // 返回第一个没有被删除的属性的索引
            return i;
    }
    // 如果在给定的关系中找不到没有被删除的属性,则默认返回 0
    return 0;
}

你可能感兴趣的:(OpenGauss,gaussdb,数据库)