【OpenGauss源码学习 —— (VecToRow)算子】

VecToRow 算子

  • 概述
  • ExecInitVecToRow 函数
    • 功能
    • 参数
    • 步骤
  • ExecVecToRow 函数
    • 功能描述
    • 参数
    • 返回值
    • 执行步骤
    • DevectorizeOneBatch 函数
  • ExecEndVecToRow 函数
  • 总结

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

概述

  OpenGaussPortalRun 函数中会实际执行相关的 DML 查询,对数据进行计算和处理。在执行过程中,所有执行算子分为两大类行存储算子向量化算子。这两类算子分别对应行存储执行引擎向量化执行引擎行存储执行引擎的上层入口是 ExecutePlan 函数,向量化执行引擎的上层人口是 ExecuteVectorizedPlan 函数。其中向量化引擎是针对列存储表的执行引擎。如果存在行存储表和列存储表的混合计算,那么行存储执行引擎和向量化执行引擎直接可以通过 VecToRowRowToVec 算子进行相互转换行存储算子执行入口函数的命名规则一般为 “Exec + 算子名” 的形式,向量化算子执行入口函数的命名规则一般为 “ExecVee十算子名” 的形式,通过这样的命名规则,可以快速地找到对应算子的函数入口。
  本文所学习的重点为 VecToRow 算子的执行流程。在 OpenGauss 数据库管理系统中,VecToRow 算子扮演着一个重要的角色,特别是在处理涉及行存储和列存储表的混合计算场景。以下是对 VecToRow 算子的作用和功能的详细概述:

VecToRow 算子的作用:
  VecToRow 算子的主要作用是在行存储执行引擎和向量化执行引擎之间提供数据转换的功能。在具体的操作中,它将向量化格式(主要用于列存储表)的数据转换为行存储格式(适用于行存储表)。这种转换对于数据库系统在处理混合类型的数据(既包括行存储表又包括列存储表)时至关重要。
VecToRow 算子的功能:

  1. 数据格式转换:
  • 将数据从列存储格式转换为行存储格式。
  • 这种转换使得向量化执行引擎处理的数据能够被行存储执行引擎理解和处理。
  1. 支持混合计算:
  • 当查询涉及同时访问行存储表和列存储表时,VecToRow 算子允许这两种数据格式在同一个查询中有效结合。
  • 通过转换,算子确保数据在不同类型的存储和处理引擎之间无缝传递。
  1. 优化查询性能:
  • 在某些情况下,向量化处理(针对列存储)对于特定类型的查询更有效率。然而,对于某些操作,如某些类型的连接或复杂的数据处理,行存储格式可能更为合适。
  • VecToRow 算子使得系统能够在这两种处理方式之间灵活转换,从而优化整体查询性能。
  1. 提高灵活性和兼容性:
  • 此算子提升了数据库系统处理不同数据模型的灵活性。
  • 它确保了即使在混合存储环境下,复杂的查询也能够正确执行,增强了系统的兼容性。

ExecInitVecToRow 函数

  ExecInitVecToRow 函数为 VecToRow 算子的执行准备了必要的状态和上下文环境。

功能

  初始化 VecToRow 算子的执行状态,用于将向量化格式的数据转换为行格式。

参数

  • VecToRow* node: VecToRow 算子节点。
  • EState* estate: 执行状态。
  • int eflags: 执行标志。

步骤

  1. 创建状态结构: 使用 makeNode 函数创建 VecToRowState 结构体。
  2. 初始化状态: 设置计划节点、执行状态和向量化状态。
  3. 结果元组槽初始化: 使用 ExecInitResultTupleSlot 初始化结果元组槽。
  4. 子节点初始化: 使用 ExecInitNode 初始化子节点,屏蔽对特定功能的需求。
  5. 记录列存储分区数: 使用 RecordCstorePartNum 记录列存储的分区数量。
  6. 表达式上下文创建: 为节点创建表达式上下文。
  7. 元组类型初始化: 设置元组类型,不需要初始化投影信息。
  8. 去向量化函数分配: 根据数据类型分配去向量化函数。
  9. 返回状态: 返回初始化完成的 VecToRowState 状态结构。

  函数源码如下所示:(路径:src\gausskernel\runtime\vecexecutor\vecnode\vectortorow.cpp

/*
 * ExecInitVecToRow 函数初始化 VecToRow 算子的执行状态。
 * 这个函数负责将向量化格式的数据转换为行格式数据。
 * 参数包括 VecToRow 算子节点、执行状态和执行标志。
 */
VecToRowState* ExecInitVecToRow(VecToRow* node, EState* estate, int eflags)
{
    VecToRowState* state = NULL;

    // 创建 VecToRowState 结构体
    state = makeNode(VecToRowState);
    state->ps.plan = (Plan*)node;     // 指定计划节点
    state->ps.state = estate;         // 设置执行状态
    state->ps.vectorized = false;     // 设置为非向量化状态

    // 初始化结果元组槽
    // VecToRow 算子不产生自己的元组,只处理子节点的元组。
    ExecInitResultTupleSlot(estate, &state->ps);

    // 初始化子节点
    // 对子节点进行初始化,屏蔽对 REWIND, BACKWARD 或 MARK/RESTORE 的需求。
    if ((uint32)eflags & EXEC_FLAG_BACKWARD)
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("column store doesn't support backward scan")));
    outerPlanState(state) = ExecInitNode(outerPlan(node), estate, eflags);

    RecordCstorePartNum(state, node); // 记录列存储的分区数量

    // 杂项初始化
    // 为节点创建表达式上下文
    ExecAssignExprContext(estate, &state->ps);

    // 初始化元组类型
    // 不需要初始化投影信息,因为这个节点不执行投影操作。
    ExecAssignResultTypeFromTL(
            &state->ps,
            ExecGetResultType(outerPlanState(state))->tdTableAmType);

    state->ps.ps_ProjInfo = NULL;
    state->m_currentRow = 0;          // 初始化当前行为0
    state->m_pCurrentBatch = NULL;    // 当前批次指针置为空
    state->nattrs = ExecGetResultType(&state->ps)->natts; // 设置属性数量
    state->tts = state->ps.ps_ResultTupleSlot;
    (void)ExecClearTuple(state->tts);
    state->tts->tts_nvalid = state->nattrs;
    state->tts->tts_isempty = false;
    state->devectorizeFunRuntime = (DevectorizeFun*)palloc0(state->nattrs * sizeof(DevectorizeFun));
    for (int i = 0; i < state->nattrs; i++) {
        state->tts->tts_isnull[i] = false;
        int type_id = state->tts->tts_tupleDescriptor->attrs[i]->atttypid;
        // 根据类型 ID 分配去向量化函数
        // 各类型数据有不同的去向量化方法
        if (COL_IS_ENCODE(type_id)) {
            switch (type_id) {
                case BPCHAROID:
                case TEXTOID:
                case VARCHAROID:
                    state->devectorizeFunRuntime[i] = DevectorizeOneColumn<VARCHAROID>;
                    break;
                case TIMETZOID:
                case TINTERVALOID:
                case INTERVALOID:
                case NAMEOID:
                case MACADDROID:
                case UUIDOID:
                    state->devectorizeFunRuntime[i] = DevectorizeOneColumn<TIMETZOID>;
                    break;
                case UNKNOWNOID:
                case CSTRINGOID:
                    state->devectorizeFunRuntime[i] = DevectorizeOneColumn<UNKNOWNOID>;
                    break;
                default:
                    state->devectorizeFunRuntime[i] = DevectorizeOneColumn<-2>;
                    break;
            }
        } else {
            if (type_id == TIDOID)
                state->devectorizeFunRuntime[i] = DevectorizeOneColumn<TIDOID>;
            else
                state->devectorizeFunRuntime[i] = DevectorizeOneColumn<-1>;
        }
    }

    state->m_ttsvalues = NULL;
    state->m_ttsisnull = NULL;

    return state; // 返回初始化好的状态
}

ExecVecToRow 函数

  ExecVecToRow 函数通过以上步骤将向量化数据批次转换为行格式数据,以便在行存储执行引擎中进一步处理。

功能描述

ExecVecToRow 函数负责执行 VecToRow 算子,将向量化批处理数据转换为行格式数据。

参数

  • VecToRowState* state: VecToRow 算子的状态结构体。

返回值

  • 返回 TupleTableSlot*:指向转换后的行格式数据的指针,或在没有更多数据时返回 NULL

执行步骤

  1. 初始化变量

    • 定义并初始化局部变量 PlanState* outer_planTupleTableSlot* tupleVectorBatch* current_batch
    • tuple_subscript 用于在批次中定位特定行。
  2. 处理当前批次

    • state 中获取当前向量化数据批次。
    • 如果当前批次为空,则从子计划节点获取新的批次。
  3. 获取新批次

    • 使用 VectorEngine 函数从子计划节点获取新的向量化数据批次。
    • 如果新批次为空,表示没有更多数据,函数返回 NULL
  4. 更新状态和转换批次

    • 将获取的新批次赋值给 state->m_pCurrentBatch 并重置 state->m_currentRow
    • 调用 DevectorizeOneBatch 函数将新批次的数据从向量化格式转换为行格式。
  5. 检索当前批次的行

    • 使用 state->m_currentRowstate->nattrs 定位当前处理的行。
    • 清空 tuple 并使用状态中的数据填充 tuple
  6. 更新行索引

    • 增加 state->m_currentRow 以处理下一行。
    • 如果已处理完当前批次的所有行,重置批次和行索引。
  7. 存储并返回行数据

    • 调用 ExecStoreVirtualTuple 将处理后的行数据存储到 tuple
    • 返回 tuple

  函数源码如下所示:(路径:src\gausskernel\runtime\vecexecutor\vecnode\vectortorow.cpp

/*
 * ExecVecToRow 函数执行 VecToRow 算子,将向量化批处理数据转换为行格式数据。
 * 参数 state 是 VecToRowState 结构体,包含算子的当前状态。
 * 函数返回一个 TupleTableSlot 指针,指向转换后的行格式数据,或在没有更多数据时返回 NULL。
 */
TupleTableSlot* ExecVecToRow(VecToRowState* state) /* return: a tuple or NULL */
{
    PlanState* outer_plan = NULL;
    TupleTableSlot* tuple = state->tts;
    VectorBatch* current_batch = NULL;
    int tuple_subscript;

    // 获取当前批次的数据
    current_batch = state->m_pCurrentBatch;
    if (BatchIsNull(current_batch)) {
        // 如果当前批次为空,从子计划节点获取新的批次
        outer_plan = outerPlanState(state);
        current_batch = VectorEngine(outer_plan);
        if (BatchIsNull(current_batch))  // 如果没有更多行,则返回 NULL
            return NULL;

        // 更新状态,记录新的批次和行索引
        state->m_pCurrentBatch = current_batch;
        state->m_currentRow = 0;
        // 将当前批次的数据从向量格式转换为行格式
        DevectorizeOneBatch(state);
    }

    // 从当前批次中检索行
    tuple_subscript = state->m_currentRow * state->nattrs;
    (void)ExecClearTuple(tuple);
    for (int i = 0; i < state->nattrs; i++) {
        // 从状态中获取转换后的行数据
        tuple->tts_values[i] = state->m_ttsvalues[tuple_subscript + i];
        tuple->tts_isnull[i] = state->m_ttsisnull[tuple_subscript + i];
    }
    state->m_currentRow++;

    if (state->m_currentRow >= current_batch->m_rows) {
        // 如果当前批次的所有行都已处理,标记批次为空
        current_batch->m_rows = 0;
        state->m_currentRow = 0;
    }

    // 将行数据存储到元组槽中
    ExecStoreVirtualTuple(tuple);
    return tuple; // 返回处理后的行数据
}

DevectorizeOneBatch 函数

  DevectorizeOneBatch 主要负责将数据从向量化的存储格式转换为行式格式,这是在将数据从列存储表传递给行存储执行引擎时一个关键的步骤。它通过逐列处理当前批次中的数据,将向量化的列数据转换为标准的行式数据,以便于行存储执行引擎进一步处理。函数源码如下所示:(路径:src\gausskernel\runtime\vecexecutor\vecnode\vectortorow.cpp

/* 将整个批次从向量存储转换为行存储的函数 */
void DevectorizeOneBatch(VecToRowState* state)
{
    int i;
    int j;
    int rows;
    int cols;
    VectorBatch* current_batch = NULL;
    ScalarVector* column = NULL;
    MemoryContext old_context;

    current_batch = state->m_pCurrentBatch;
    rows = current_batch->m_rows; // 当前批次的行数
    cols = state->nattrs;         // 列数

    /* 为 m_ttsvalues 分配内存,用于存储列值;
     * 为 m_ttsisnull 分配内存,用于指示列值是否为 null。
     * 这两个数组都存放在 VecToRowState 中。 */
    if (state->m_ttsvalues == NULL) {
        state->m_ttsvalues = (Datum*)palloc(sizeof(Datum) * cols * BatchMaxSize);
        state->m_ttsisnull = (bool*)palloc(sizeof(bool) * cols * BatchMaxSize);
    }

    /* 循环处理整个批次,逐列进行。 */
    ExprContext* econtext = state->ps.ps_ExprContext; // 表达式上下文
    ResetExprContext(econtext); // 重置表达式上下文

    old_context = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
    for (i = 0; i < cols; i++) {
        column = &current_batch->m_arr[i]; // 获取当前列的向量

        /* 处理常量情况;同时处理 NULL 值情况。 */
        for (j = 0; j < rows; j++)
            state->m_ttsisnull[j * cols + i] = IS_NULL(column->m_flag[j]);

        // 调用去向量化函数处理当前列
        state->devectorizeFunRuntime[i](state, column, rows, cols, i);
    }

    // 恢复原始的内存上下文
    (void)MemoryContextSwitchTo(old_context);
    return;
}

ExecEndVecToRow 函数

  ExecEndVecToRow 函数的作用和功能是在执行数据库查询计划时进行清理和资源释放。首先,它清空了与该节点关联的结果元组槽,以确保在下一次使用前不会残留任何旧数据。接下来,它关闭了与该节点关联的子计划,这些子计划是嵌套在主查询中的子查询或连接操作,执行结束后需要关闭以释放资源。这些操作是确保查询执行的正确性和资源管理的重要步骤。ExecEndVecToRow 函数源码如下所示:(路径:src\gausskernel\runtime\vecexecutor\vecnode\vectortorow.cpp

void ExecEndVecToRow(VecToRowState* node)
{
	// 清空元组表
	// 这行代码清空了与给定节点关联的结果元组槽。
	// 结果元组槽通常用于存储查询结果的临时数据结构。
	(void)ExecClearTuple(node->ps.ps_ResultTupleSlot);
	
	// 关闭子计划
	// 这行代码关闭了与给定节点关联的子计划。
	// 子计划是查询计划中的嵌套计划,通常是子查询或连接操作。
	// 执行结束后需要关闭这些子计划以释放资源。
	ExecEndNode(outerPlanState(node));
}

总结

  VecToRow 算子在 OpenGauss 的架构中起着桥梁的作用,连接了行存储和向量化(列存储)执行引擎。它通过转换数据格式,确保了数据可以在这两种不同的存储和处理模型之间流动,从而支持更复杂和多样化的查询操作。这不仅提高了查询的灵活性和效率,还增强了数据库在处理不同类型数据时的整体性能。

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