海山数据库(He3DB)技术分享:计划器与执行器

一、概述

He3DB是一个功能强大的开源关系型数据库管理系统,其查询处理过程可以分为几个主要阶段:解析、重写、规划(计划生成)、优化和执行。本文档将重点讨论计划器和执行器的设计,包括相关代码和结构体分析。

二、计划器

1.功能:
   计划器的核心任务是将用户提交的SQL查询转换为高效的执行计划。执行计划是一个由多个计划节点组成的树形结构,每个节点代表一种特定的数据操作(如扫描、连接、排序等)。

2.结构体分析
PlannerInfo: 这是计划器的主要数据结构,包含了规划过程中所需的所有信息,如查询树、关系列表、统计信息等。

struct PlannerInfo
{
 NodeTag  type;  //这个字段用于标识节点的类型,是 He3DB 内部数据结构的通用标记。

 Query    *parse;   //指向表示查询的抽象语法树(AST)的指针。它是用户提交的原始SQL查询的解析结果。
 PlannerGlobal *glob;  //指向全局规划信息的指针。这些信息是跨计划器树共享的,比如统计信息和全局配置。
 Index  query_level; //当前计划器树的深度,用于多层嵌套查询的处理。
 PlannerInfo *parent_root; //指向父查询的计划信息,主要用于处理嵌套查询和子查询。
 List    *plan_params;  //保存计划参数的列表。这些参数是用于生成执行计划的动态值。
Bitmapset  *outer_params;  //存储当前查询中外部参数的位图集合。用于表示查询参数的集合,用于生成计划。

 struct RelOptInfo **simple_rel_array;  //指向一个数组的指针,该数组包含当前查询中所有基本关系(即表)的优化信息。
 int   simple_rel_array_size;  // 数组的大小,表示当前查询中基本关系的数量。
 RangeTblEntry **simple_rte_array; //指向一个数组的指针,该数组包含 RangeTblEntry 结构体,描述了查询中的所有表或视图。

 struct AppendRelInfo **append_rel_array; //指向一个数组的指针,包含所有附加关系的信息。附加关系用于处理子查询和联合查询中的关系。

 Relids  all_baserels;  //保存查询中所有基本关系的标识符集合。用于跟踪在查询中参与的所有基本表。

 Relids  nullable_baserels;  //保存可能为空的基本关系的标识符集合。用于处理外连接等可能产生空值的情况。

 List    *join_rel_list;  //包含所有连接关系的列表。连接关系是在查询优化过程中生成的,用于描述如何将多个关系连接在一起。
struct HTAB *join_rel_hash;  //哈希表,用于快速查找连接关系的信息。
 List   **join_rel_level;  //指向一个列表的指针,列表的每个元素表示在某一连接层级上的连接关系。用于处理不同层级的连接优化。
 int  join_cur_level;  //当前连接层级的索引,用于管理和优化连接的顺序。
List    *init_plans; //存储初始化计划的列表,通常用于处理具有初始化子查询的查询。

 List    *cte_plan_ids;  //包含所有公用表表达式(CTE)计划ID的列表。用于处理CTE中的子查询。

 List    *multiexpr_params; //存储多个表达式参数的列表。这些参数用于表达式的计算和优化。

 List    *eq_classes;  //存储等值类(用于优化连接操作)的列表。等值类用于描述连接条件中的等号关系。
 bool  ec_merging_done; //标记等值类的合并是否已经完成。用于优化连接条件。

 List    *canon_pathkeys;  //存储标准化路径键的列表。路径键用于排序和索引优化。
 List    *left_join_clauses; //存储左连接条件的列表。用于优化左连接操作。

 List    *right_join_clauses;  //存储右连接条件的列表。用于优化右连接操作。

 List    *full_join_clauses; //存储全连接条件的列表。用于优化全连接操作。

 List    *join_info_list; //存储连接信息的列表,包含有关连接操作的额外信息。
 Relids  all_result_relids; //存储所有结果关系的标识符集合。用于跟踪查询结果中涉及的关系。
 Relids  leaf_result_relids;  //存储叶节点结果关系的标识符集合。用于跟踪查询的基本关系。
 List    *append_rel_list; //存储附加关系的列表,主要用于处理继承或联合查询中的附加关系。

 List    *row_identity_vars; //存储行标识符变量的列表,用于处理特定的行标识符。

 List    *rowMarks; //存储行标记的列表,用于处理并发更新和锁定操作。

 List    *placeholder_list; //存储占位符的列表,用于处理复杂查询中的占位符参数。

 List    *fkey_list; //存储外键约束的列表,用于优化外键相关的操作。

 List    *query_pathkeys;  //存储查询路径键的列表,用于优化查询的排序和索引。

 List    *group_pathkeys;  //存储分组路径键的列表,用于优化分组操作。
 List    *window_pathkeys;  //存储窗口函数路径键的列表,用于优化窗口函数的执行。
 List    *distinct_pathkeys;   //存储去重路径键的列表,用于优化去重操作。
 List    *sort_pathkeys; //存储排序路径键的列表,用于优化排序操作。

 List    *part_schemes; //存储分区方案的列表,用于优化分区表的查询。

 List    *initial_rels; //存储初始关系的列表,用于处理查询初始阶段的关系信息。

 List    *upper_rels[UPPERREL_FINAL + 1]; //存储每个上层优化阶段的关系列表。UPPERREL_FINAL 是一个宏,表示最终的优化阶段。

 struct PathTarget *upper_targets[UPPERREL_FINAL + 1]; //存储每个上层优化阶段的目标路径。用于描述优化目标的结构体。

 List    *processed_tlist; //存储处理后的目标列表,用于描述查询结果的结构。

 List    *update_colnos;  //存储需要更新的列编号列表。用于处理更新操作的列。

 AttrNumber *grouping_map; //存储分组映射的数组,用于处理分组操作中的列映射。
 List    *minmax_aggs; //存储最小最大聚合的信息列表,用于优化聚合操作中的最小最大值计算。

 MemoryContext planner_cxt;  //存储计划器使用的内存上下文,用于管理内存分配和释放。
 
 Cardinality total_table_pages;  //存储总的表页数,用于估算查询的成本和优化计划。

 Selectivity tuple_fraction; //存储元组选择性,用于估算查询结果的大小。

 Cardinality limit_tuples;  //存储查询结果的限制元组数,用于限制返回的结果集大小。

 Index  qual_security_level; //存储查询中资格的安全级别,用于处理安全相关的查询优化。
 

 bool  hasJoinRTEs;  //标记是否存在连接RTE(Range Table Entries),用于优化连接操作。

 bool  hasLateralRTEs;  //标记是否存在LATERAL RTE,用于处理LATERAL子查询的优化。

 bool  hasHavingQual; //标记是否存在HAVING子句,用于优化HAVING子句的处理。

 bool  hasPseudoConstantQuals;//标记是否存在伪常量资格,用于优化查询中的常量条件。

 bool  hasAlternativeSubPlans; //标记是否存在备用子计划,用于处理备用查询计划。

 bool  hasRecursion;  //标记是否存在递归查询,用于处理递归查询的优化。

 List    *agginfos;  //存储聚合信息的列表,用于处理聚合函数的优化。

 List    *aggtransinfos; //存储聚合转换信息的列表,用于处理聚合函数的转换操作。

 int   numOrderedAggs;  //存储有序聚合的数量,用于优化聚合操作的顺序。

 bool  hasNonPartialAggs; //标记是否存在非部分聚合,用于处理全量聚合操作。
 bool  hasNonSerialAggs;  //标记是否存在非串行聚合,用于处理串行聚合操作。

 int   wt_param_id; //存储工作线程参数ID,用于处理并发查询的工作线程。

 struct Path *non_recursive_path; //存储非递归路径,用于处理递归查询的非递归部分

 Relids  curOuterRels; //存储当前外部关系的标识符集合,用于处理嵌套查询中的外部关系。

 List    *curOuterParams; //存储当前外部参数的列表,用于处理嵌套查询中的外部参数。

 bool    *isAltSubplan; //标记是否存在备用子计划的数组,用于优化备用子计划的使用。

 bool    *isUsedSubplan; //标记子计划是否已被使用的数组,用于跟踪子计划的使用情况。

 
 void    *join_search_private; //用于存储连接搜索的私有数据。可以是优化过程中使用的各种数据结构。

 bool  partColsUpdated; //标记是否更新了分区列,用于处理分区表的查询优化。
}

Plan: 表示执行计划的基类,所有具体的计划节点类型都继承自这个类。

typedef struct Plan
{
 NodeTag  type;  //这是一个标识字段,用于标识节点的类型。NodeTag 是一个枚举或标识符,用于区分不同类型的节点,例如扫描、连接、排序等。

 Cost  startup_cost;  //这是执行该计划节点的启动成本,通常包括初始化所需的资源和时间等。

Cost  total_cost;  //这是执行该计划节点的总成本,通常是 startup_cost 和后续处理成本的总和。
 
Cardinality plan_rows; //这是该计划节点预计处理的行数。这有助于估算查询的规模和效率。
 
 int   plan_width; //这是每行的宽度(以字节为单位)。它有助于计算内存需求和传输成本。
 
 bool  parallel_aware; //这是一个布尔值,指示该计划节点是否对并行执行有意识。如果为 true,则该节点可能会考虑并行处理的选项。

 bool  parallel_safe; //这是一个布尔值,指示该计划节点是否安全地在并行模式下执行。如果为 true,则该节点在并行执行时不会引入问题。

 bool  async_capable;  //这是一个布尔值,指示该计划节点是否支持异步执行。如果为 true,则该节点能够异步处理请求。

int   plan_node_id; //这是一个整数,用于唯一标识该计划节点。它在计划树中可能用于追踪或引用特定节点。

List    *targetlist;   //这是一个指向 List 的指针,表示该计划节点要返回的目标列表。目标列表通常包含要选择的列和表达式。

List    *qual;  // 这是一个指向 List 的指针,表示计划节点的查询条件或谓词列表。这些条件用于过滤数据。 
 
struct Plan *lefttree; // 是一个指向另一个 Plan 结构体的指针,表示该节点的左子树。通常用于树形结构的计划表示,例如连接操作的左子操作。 

struct Plan *righttree; //这是一个指向另一个 Plan 结构体的指针,表示该节点的右子树。通常用于树形结构的计划表示,例如连接操作的右子操作。
 
List    *initPlan;  // 这是一个指向 List 的指针,表示在执行计划节点之前需要初始化的计划列表。初始化计划通常包括初始化变量或计算初始值。

 Bitmapset  *extParam; //这是一个指向 Bitmapset 的指针,表示该节点使用的外部参数。Bitmapset 是一个位图集合,用于表示使用的外部参数的集合。

 Bitmapset  *allParam; //这是一个指向 Bitmapset 的指针,表示该节点可能使用的所有参数的集合。Bitmapset 是一个位图集合,用于表示节点可能需要的所有参数。
} Plan;

3.代码分析
   计划器的入口函数通常是standard_planner(),它接受一个查询树作为输入,并返回生成的执行计划。在计划生成过程中,会涉及多个复杂的函数和算法,如统计信息查找、连接顺序选择、连接方法选择等。

PlannedStmt *
standard_planner(Query *parse, const char *query_string, int cursorOptions,
     ParamListInfo boundParams)
{
 PlannedStmt *result = NULL;
 PlannerGlobal *glob = NULL;
 double tuple_fraction = 0.0;
 PlannerInfo *root = NULL;
 RelOptInfo *final_rel = NULL;
 Path *best_path = NULL;
 Plan *top_plan = NULL;
 ListCell *lp,
  *lr;

 glob = makeNode(PlannerGlobal);  //PlannerGlobal 结构体用于在查询优化的过程中存储全局信息,如绑定参数、子计划等。

 glob->boundParams = boundParams;
 glob->subplans = NIL;
 glob->subroots = NIL;
 glob->rewindPlanIDs = NULL;
 glob->finalrtable = NIL;
 glob->finalrowmarks = NIL;
 glob->resultRelations = NIL;
 glob->appendRelations = NIL;
 glob->relationOids = NIL;
 glob->invalItems = NIL;
 glob->paramExecTypes = NIL;
 glob->lastPHId = 0;
 glob->lastRowMarkId = 0;
 glob->lastPlanNodeId = 0;
 glob->transientPlan = false;
 glob->dependsOnRole = false;

 if ((cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 &&
  IsUnderPostmaster &&
  parse->commandType == CMD_SELECT &&
  !parse->hasModifyingCTE &&
  max_parallel_workers_per_gather > 0 &&
  !IsParallelWorker())
 {
  glob->maxParallelHazard = max_parallel_hazard(parse);
  glob->parallelModeOK = (glob->maxParallelHazard != PROPARALLEL_UNSAFE);
 }
 else
 {
  /* skip the query tree scan, just assume it's unsafe */
  glob->maxParallelHazard = PROPARALLEL_UNSAFE;
  glob->parallelModeOK = false;
 }
//如果查询可以并行执行,则根据查询的并行安全性和其他条件来设置 parallelModeOK 标志。
 glob->parallelModeNeeded = glob->parallelModeOK &&
          (force_parallel_mode != FORCE_PARALLEL_OFF);

 if (cursorOptions & CURSOR_OPT_FAST_PLAN)
 {
 
  tuple_fraction = cursor_tuple_fraction;

  if (tuple_fraction >= 1.0)
   tuple_fraction = 0.0;
  else if (tuple_fraction <= 0.0)
   tuple_fraction = 1e-10;
 }
 else
 {
  tuple_fraction = 0.0;
 }
//tuple_fraction 用于估算计划的成本,决定是否启用快速计划选项。
 root = subquery_planner(glob, parse, NULL,
       false, tuple_fraction);//生成查询的子查询计划,并返回 PlannerInfo 结构体。

 final_rel = fetch_upper_rel(root, UPPERREL_FINAL, NULL);
 best_path = get_cheapest_fractional_path(final_rel, tuple_fraction); //从最终的关系中选择成本最低的路径。

 top_plan = create_plan(root, best_path);  //创建执行计划:

 if (cursorOptions & CURSOR_OPT_SCROLL) //处理光标选项:
 {
  if (!ExecSupportsBackwardScan(top_plan))
   top_plan = materialize_finished_plan(top_plan);
 }

 if (force_parallel_mode != FORCE_PARALLEL_OFF && top_plan->parallel_safe) //处理并行计划:
 {
  Gather *gather = makeNode(Gather);

 
  Assert(top_plan->initPlan == NIL);

  gather->plan.targetlist = top_plan->targetlist;
  gather->plan.qual = NIL;
  gather->plan.lefttree = top_plan;
  gather->plan.righttree = NULL;
  gather->num_workers = 1;
  gather->single_copy = true;
  gather->invisible = (force_parallel_mode == FORCE_PARALLEL_REGRESS);
  gather->rescan_param = -1;

  /*
   * Ideally we'd use cost_gather here, but setting up dummy path data
   * to satisfy it doesn't seem much cleaner than knowing what it does.
   */
  gather->plan.startup_cost = top_plan->startup_cost +
         parallel_setup_cost;
  gather->plan.total_cost = top_plan->total_cost +
          parallel_setup_cost + parallel_tuple_cost * top_plan->plan_rows;
  gather->plan.plan_rows = top_plan->plan_rows;
  gather->plan.plan_width = top_plan->plan_width;
  gather->plan.parallel_aware = false;
  gather->plan.parallel_safe = false;

  /* use parallel mode for parallel plans. */
  root->glob->parallelModeNeeded = true;

  top_plan = &gather->plan;
 }
 if (glob->paramExecTypes != NIL)
 {
  Assert(list_length(glob->subplans) == list_length(glob->subroots));
  forboth(lp, glob->subplans, lr, glob->subroots)
  {
   Plan *subplan = (Plan *)lfirst(lp);
   PlannerInfo *subroot = lfirst_node(PlannerInfo, lr);

   SS_finalize_plan(subroot, subplan);
  }
  SS_finalize_plan(root, top_plan);
 }

 Assert(glob->finalrtable == NIL);
 Assert(glob->finalrowmarks == NIL);
 Assert(glob->resultRelations == NIL);
 Assert(glob->appendRelations == NIL);
 top_plan = set_plan_references(root, top_plan);
 Assert(list_length(glob->subplans) == list_length(glob->subroots));
 forboth(lp, glob->subplans, lr, glob->subroots)
 {
  Plan *subplan = (Plan *)lfirst(lp);
  PlannerInfo *subroot = lfirst_node(PlannerInfo, lr);

  lfirst(lp) = set_plan_references(subroot, subplan);
 }

 /* 构建 PlannedStmt 结果: */
 result = makeNode(PlannedStmt);

 result->commandType = parse->commandType;
 result->queryId = parse->queryId;
 result->hasReturning = (parse->returningList != NIL);
 result->hasModifyingCTE = parse->hasModifyingCTE;
 result->canSetTag = parse->canSetTag;
 result->transientPlan = glob->transientPlan;
 result->dependsOnRole = glob->dependsOnRole;
 result->parallelModeNeeded = glob->parallelModeNeeded;
 result->planTree = top_plan;
 result->rtable = glob->finalrtable;
 result->resultRelations = glob->resultRelations;
 result->appendRelations = glob->appendRelations;
 result->subplans = glob->subplans;
 result->rewindPlanIDs = glob->rewindPlanIDs;
 result->rowMarks = glob->finalrowmarks;
 result->relationOids = glob->relationOids;
 result->invalItems = glob->invalItems;
 result->paramExecTypes = glob->paramExecTypes;
 result->utilityStmt = parse->utilityStmt;
 result->stmt_location = parse->stmt_location;
 result->stmt_len = parse->stmt_len;

 result->jitFlags = PGJIT_NONE;
 if (jit_enabled && jit_above_cost >= 0 &&
  top_plan->total_cost > jit_above_cost)
 {
  result->jitFlags |= PGJIT_PERFORM;

 
  if (jit_optimize_above_cost >= 0 &&
   top_plan->total_cost > jit_optimize_above_cost)
   result->jitFlags |= PGJIT_OPT3;
  if (jit_inline_above_cost >= 0 &&
   top_plan->total_cost > jit_inline_above_cost)
   result->jitFlags |= PGJIT_INLINE;

  /*
   * Decide which operations should be JITed.
   */
  if (jit_expressions)
   result->jitFlags |= PGJIT_EXPR;
  if (jit_tuple_deforming)
   result->jitFlags |= PGJIT_DEFORM;
 }
//处理 JIT 编译选项:

 if (glob->partition_directory != NULL)
  DestroyPartitionDirectory(glob->partition_directory);

 return result;
}

三、执行器

1.功能
   执行器的任务是按照生成的执行计划执行查询,并返回结果。它负责协调各个计划节点的执行顺序和数据流动。

2.结构体分析
   EState:结构体是 He3DB 执行器(Executor)中的一个关键结构,负责维护查询执行的状态信息。它包含了查询的执行环境、相关的上下文信息以及处理查询所需的各种状态信息。

typedef struct EState
{
    NodeTag type;  // 用于标识结构体的类型(通常用于节点树中)。
    
    /* Basic state for all query types: */
    ScanDirection es_direction;  // 当前的扫描方向(例如:正向、反向)。
    Snapshot es_snapshot;  // 用于查询的快照(时间点)。
    Snapshot es_crosscheck_snapshot;  // 用于引用完整性检查的快照。
    List *es_range_table;  // 范围表列表,每个条目是一个 RangeTblEntry 结构。
    Index es_range_table_size;  // 范围表数组的大小。
    Relation *es_relations;  // 表的关系指针数组,可能为 NULL(尚未打开)。
    struct ExecRowMark **es_rowmarks;  // 每个范围表条目的 ExecRowMarks 数组,可能为 NULL。
    PlannedStmt *es_plannedstmt;  // 指向计划树的顶部。
    const char *es_sourceText;  // 源查询文本。

    JunkFilter *es_junkFilter;  // 顶层的垃圾数据过滤器(如果存在)。

    /* If query can insert/delete tuples, the command ID to mark them with */
    CommandId es_output_cid;  // 用于标记插入/删除元组的命令 ID。

    /* Info about target table(s) for insert/update/delete queries: */
    ResultRelInfo **es_result_relations;  // 针对插入/更新/删除查询的目标表的 ResultRelInfo 指针数组,可能为 NULL。
    List *es_opened_result_relations;  // 非 NULL 的 es_result_relations 条目的列表(无特定顺序)。

    PartitionDirectory es_partition_directory;  // 分区目录,用于查找 PartitionDesc。

    /*
     * The following list contains ResultRelInfos created by the tuple routing
     * code for partitions that aren't found in the es_result_relations array.
     */
    List *es_tuple_routing_result_relations;  // 用于分区的 ResultRelInfos 列表,未在 es_result_relations 数组中找到。

    /* Stuff used for firing triggers: */
    List *es_trig_target_relations;  // 触发器专用的 ResultRelInfos 列表。

    /* Parameter info: */
    ParamListInfo es_param_list_info;  // 外部参数的值。
    ParamExecData *es_param_exec_vals;  // 内部参数的值。

    QueryEnvironment *es_queryEnv;  // 查询环境。

    /* Other working state: */
    MemoryContext es_query_cxt;  // 执行 EState 的每个查询的内存上下文。

    List *es_tupleTable;  // TupleTableSlots 的列表。

    uint64 es_processed;  // 已处理的元组数量。

    int es_top_eflags;  // 传递给 ExecutorStart 的 eflags。
    int es_instrument;  // InstrumentOption 标志的 OR 运算结果。
    bool es_finished;  // ExecutorFinish 是否完成的标志。

    List *es_exprcontexts;  // EState 中的 ExprContexts 列表。

    List *es_subplanstates;  // SubPlans 的 PlanState 列表。

    List *es_auxmodifytables;  // 次级 ModifyTableStates 的列表。

    /*
     * this ExprContext is for per-output-tuple operations, such as constraint
     * checks and index-value computations.  It will be reset for each output
     * tuple.  Note that it will be created only if needed.
     */
    ExprContext *es_per_tuple_exprcontext;  // 每个输出元组操作的 ExprContext,仅在需要时创建。

    /*
     * If not NULL, this is an EPQState's EState. This is a field in EState
     * both to allow EvalPlanQual aware executor nodes to detect that they
     * need to perform EPQ related work, and to provide necessary information
     * to do so.
     */
    struct EPQState *es_epq_active;  // 非 NULL 表示这是一个 EPQState 的 EState,允许执行器节点执行 EPQ 相关的工作。

    bool es_use_parallel_mode;  // 是否可以使用并行工作者。

    /* The per-query shared memory area to use for parallel execution. */
    struct dsa_area *es_query_dsa;  // 查询的共享内存区域,用于并行执行。

    /*
     * JIT information. es_jit_flags indicates whether JIT should be performed
     * and with which options.  es_jit is created on-demand when JITing is
     * performed.
     *
     * es_jit_worker_instr is the combined, on demand allocated,
     * instrumentation from all workers. The leader's instrumentation is kept
     * separate, and is combined on demand by ExplainPrintJITSummary().
     */
    int es_jit_flags;  // JIT 是否应执行以及选项的标志。
    struct JitContext *es_jit;  // JIT 上下文,仅在需要 JIT 时创建。
    struct JitInstrumentation *es_jit_worker_instr;  // 所有工作者的合并 JIT 仪表数据。

    /*
     * Lists of ResultRelInfos for foreign tables on which batch-inserts are
     * to be executed and owning ModifyTableStates, stored in the same order.
     */
    List *es_insert_pending_result_relations;  // 批量插入将要执行的外表的 ResultRelInfos 列表。
    List *es_insert_pending_modifytables;  // 拥有 ModifyTableStates 的外表列表,按相同顺序存储。

    /* List of ResultRelInfoExtra structs (see above) */
    List *es_resultrelinfo_extra;  // ResultRelInfoExtra 结构的列表。
} EState;

PlanState:表示执行计划中的每个节点状态。

typedef struct PlanState
{
 NodeTag  type;     /* 节点类型标识符 */

 Plan    *plan;     /* 关联的 Plan 节点 */

 EState    *state;     /* 执行时,单个节点的状态指向整个顶级计划的 EState */

 ExecProcNodeMtd ExecProcNode;  /* 函数指针,用于返回下一个元组 */
 ExecProcNodeMtd ExecProcNodeReal; /* 实际函数指针,如果 ExecProcNode 是包装器 */

 Instrumentation *instrument;  /* 可选的运行时统计信息,用于该节点 */
 WorkerInstrumentation *worker_instrument; /* 每个工作线程的统计信息 */

 /* 每个工作线程的 JIT 编译统计信息 */
 struct SharedJitInstrumentation *worker_jit_instrument;

 /*
  * 所有 Plan 类型的公共结构数据。这些链接到辅助状态树,类似于
  * 关联计划树中的链接(除了 subPlan 列表,它在计划树中不存在)。
  */
 ExprState  *qual;     /* 布尔条件表达式 */
 struct PlanState *lefttree;  /* 输入计划树的左子树 */
 struct PlanState *righttree;  /* 输入计划树的右子树 */

 List    *initPlan;    /* 初始化的 SubPlanState 节点(未关联的表达式子查询) */
 List    *subPlan;    /* 表达式中的 SubPlanState 节点 */

 /*
  * 管理参数变化驱动的重新扫描的状态
  */
 Bitmapset  *chgParam;    /* 改变的参数的 ID 集合 */

 /*
  * 大多数节点类型所需的其他运行时状态
  */
 TupleDesc ps_ResultTupleDesc;  /* 节点的返回类型描述 */
 TupleTableSlot *ps_ResultTupleSlot; /* 存放结果元组的槽 */
 ExprContext *ps_ExprContext;  /* 节点的表达式评估上下文 */
 ProjectionInfo *ps_ProjInfo;  /* 元组投影的信息 */

 bool  async_capable;   /* 如果节点支持异步操作,则为真 */

 /*
  * 如果已知,扫描槽的描述符。如果没有此描述符,表达式编译
  * 很难基于描述符进行优化,除非对所有执行节点都有知识。
  */
 TupleDesc scandesc;

 /*
  * 定义与此状态作为父节点的表达式上下文相关的内部、外部和扫描槽的类型。
  * 如果 *opsset 被设置,则 *opsfixed 表示 *ops 是否保证为槽的类型。
  * 这意味着在评估表达式时,ExprContext.ecxt_*tuple 中的每个槽都会指向该类型的槽。
  * 如果 *opsfixed 为假,但 *ops 已设置,则表示槽的最可能类型。
  *
  * scan* 字段由 ExecInitScanTupleSlot() 设置。如果没有调用此函数,节点可以自己初始化这些字段。
  *
  * 如果 outer/inneropsset 为假,则使用 ExecGetResultSlotOps() 在 ->righttree/lefttree 上推断信息,使用相应节点的 resultops* 字段。
  *
  * result* 字段在使用 ExecInitResultSlot 时自动设置(无论是直接使用还是通过 ExecAssignScanProjectionInfo() /
  * ExecConditionalAssignProjectionInfo() 创建的槽)。如果不需要投影,ExecConditionalAssignProjectionInfo() 将这些字段默认设置为扫描操作。
  */
 const TupleTableSlotOps *scanops; /* 扫描槽操作类型 */
 const TupleTableSlotOps *outerops; /* 外部槽操作类型 */
 const TupleTableSlotOps *innerops; /* 内部槽操作类型 */
 const TupleTableSlotOps *resultops; /* 结果槽操作类型 */
 bool  scanopsfixed;   /* 扫描槽操作类型是否固定 */
 bool  outeropsfixed;   /* 外部槽操作类型是否固定 */
 bool  inneropsfixed;   /* 内部槽操作类型是否固定 */
 bool  resultopsfixed;   /* 结果槽操作类型是否固定 */
 bool  scanopsset;    /* 是否设置了扫描槽操作类型 */
 bool  outeropsset;    /* 是否设置了外部槽操作类型 */
 bool  inneropsset;    /* 是否设置了内部槽操作类型 */
 bool  resultopsset;    /* 是否设置了结果槽操作类型 */
} PlanState;

ResultRelInfo结果关系信息,用于结果处理和存储。

typedef struct ResultRelInfo
{
 NodeTag  type; // 结构体的节点类型标识

 /* result relation's range table index, or 0 if not in range table */
 Index  ri_RangeTableIndex; // 结果关系的范围表索引,如果不在范围表中则为0

 /* relation descriptor for result relation */
 Relation ri_RelationDesc; // 结果关系的关系描述符

 /* # of indices existing on result relation */
 int   ri_NumIndices; // 结果关系上存在的索引数量

 /* array of relation descriptors for indices */
 RelationPtr ri_IndexRelationDescs; // 索引的关系描述符数组

 /* array of key/attr info for indices */
 IndexInfo **ri_IndexRelationInfo; // 索引的键/属性信息数组

 /*
  * For UPDATE/DELETE result relations, the attribute number of the row
  * identity junk attribute in the source plan's output tuples
  */
 AttrNumber ri_RowIdAttNo; // 对于 UPDATE/DELETE 操作,源计划的输出元组中的行标识垃圾属性的属性编号

 /* Projection to generate new tuple in an INSERT/UPDATE */
 ProjectionInfo *ri_projectNew; // 用于生成 INSERT/UPDATE 操作中新元组的投影信息

 /* Slot to hold that tuple */
 TupleTableSlot *ri_newTupleSlot; // 存放新元组的槽

 /* Slot to hold the old tuple being updated */
 TupleTableSlot *ri_oldTupleSlot; // 存放被更新的旧元组的槽

 /* Have the projection and the slots above been initialized? */
 bool  ri_projectNewInfoValid; // 投影和槽是否已经初始化的标志

 /* triggers to be fired, if any */
 TriggerDesc *ri_TrigDesc; // 触发器描述信息

 /* cached lookup info for trigger functions */
 FmgrInfo   *ri_TrigFunctions; // 触发器函数的缓存信息

 /* array of trigger WHEN expr states */
 ExprState **ri_TrigWhenExprs; // 触发器 WHEN 表达式状态的数组

 /* optional runtime measurements for triggers */
 Instrumentation *ri_TrigInstrument; // 触发器的可选运行时测量信息

 /* On-demand created slots for triggers / returning processing */
 TupleTableSlot *ri_ReturningSlot; // 用于触发器输出元组的槽
 TupleTableSlot *ri_TrigOldSlot; // 用于触发器旧元组的槽
 TupleTableSlot *ri_TrigNewSlot; // 用于触发器新元组的槽

 /* FDW callback functions, if foreign table */
 struct FdwRoutine *ri_FdwRoutine; // 外部数据包装器(FDW)的回调函数

 /* available to save private state of FDW */
 void    *ri_FdwState; // 用于保存 FDW 私有状态的指针

 /* true when modifying foreign table directly */
 bool  ri_usesFdwDirectModify; // 当直接修改外部表时为真

 /* batch insert stuff */
 int   ri_NumSlots; // 批量插入中槽的数量
 int   ri_NumSlotsInitialized; // 已初始化槽的数量
 int   ri_BatchSize; // 单次批量插入的最大槽数
 TupleTableSlot **ri_Slots; // 批量插入的输入元组槽
 TupleTableSlot **ri_PlanSlots; // 计划中的槽

 /* list of WithCheckOption's to be checked */
 List    *ri_WithCheckOptions; // 待检查的 WithCheckOption 列表

 /* list of WithCheckOption expr states */
 List    *ri_WithCheckOptionExprs; // WithCheckOption 表达式状态的列表

 /* array of constraint-checking expr states */
 ExprState **ri_ConstraintExprs; // 约束检查表达式状态的数组

 /* array of stored generated columns expr states */
 ExprState **ri_GeneratedExprs; // 存储生成列表达式状态的数组

 /* number of stored generated columns we need to compute */
 int   ri_NumGeneratedNeeded; // 需要计算的存储生成列的数量

 /* list of RETURNING expressions */
 List    *ri_returningList; // RETURNING 表达式的列表

 /* for computing a RETURNING list */
 ProjectionInfo *ri_projectReturning; // 用于计算 RETURNING 列表的投影信息

 /* list of arbiter indexes to use to check conflicts */
 List    *ri_onConflictArbiterIndexes; // 用于检查冲突的仲裁索引列表

 /* ON CONFLICT evaluation state */
 OnConflictSetState *ri_onConflict; // ON CONFLICT 评估状态

 /* for MERGE, lists of MergeActionState */
 List    *ri_matchedMergeAction; // 匹配的 MERGE 操作状态列表
 List    *ri_notMatchedMergeAction; // 不匹配的 MERGE 操作状态列表

 /* partition check expression state (NULL if not set up yet) */
 ExprState  *ri_PartitionCheckExpr; // 分区检查表达式状态(如果尚未设置则为 NULL)

 /*
  * Information needed by tuple routing target relations
  *
  * RootResultRelInfo gives the target relation mentioned in the query, if
  * it's a partitioned table. It is not set if the target relation
  * mentioned in the query is an inherited table, nor when tuple routing is
  * not needed.
  *
  * RootToPartitionMap and PartitionTupleSlot, initialized by
  * ExecInitRoutingInfo, are non-NULL if partition has a different tuple
  * format than the root table.
  */
 struct ResultRelInfo *ri_RootResultRelInfo; // 查询中提到的目标关系,如果是分区表则为此信息
 TupleConversionMap *ri_RootToPartitionMap; // 将根表到分区表的元组转换映射
 TupleTableSlot *ri_PartitionTupleSlot; // 分区表的元组槽

 /*
  * Map to convert child result relation tuples to the format of the table
  * actually mentioned in the query (called "root").  Computed only if
  * needed.  A NULL map value indicates that no conversion is needed, so we
  * must have a separate flag to show if the map has been computed.
  */
 TupleConversionMap *ri_ChildToRootMap; // 将子结果关系元组转换为实际提到的表格式的映射
 bool  ri_ChildToRootMapValid; // 映射是否有效的标志

 /* for use by copyfrom.c when performing multi-inserts */
 struct CopyMultiInsertBuffer *ri_CopyMultiInsertBuffer; // 执行多插入操作时用于 copyfrom.c 的缓冲区

 /*
  * Used when a leaf partition is involved in a cross-partition update of
  * one of its ancestors; see ExecCrossPartitionUpdateForeignKey().
  */
 List    *ri_ancestorResultRels; // 当叶分区涉及到其祖先的跨分区更新时使用的祖先结果关系列表
} ResultRelInfo;

四、创建单表查询的计划树

   在创建计划树之前,计划器对先PlannerInfo中的查询树进行一些预处理。预处理有很多步骤,本节只讨论和单表查询处理相关的主要步骤。

1.创建一个RelOptInfo数据结构,存储访问路径及其代价。RelOptInfo结构体是通过make_one_rel()函数创建的,并存储于PlannerInfo结构体的simple_rel_array。字段中在初始状态时RelOptInfo持有着baserestrictinfo变量,如果存在相应索引,还会持有indexlist变量。baserestrictinfo存储着查询的WHERE子句,而indexlist存储着目标表上相关的索引。

2.估计所有可能访问路径的代价,并将访问路径添加至RelOptInfo结构中。这一处理过程的细节如下:

  • (1)创建一条路径,估计该路径中顺序扫描的代价,并将其写入路径中。将该路径添加到RelOptInfo结构的pathlist变量中。
  • (2)如果目标表上存在相关的索引,则为每个索引创建相应的索引访问路径。估计所有索引扫描的代价,并将代价写入相应路径中。然后将索引访问路径添加到pathlist变量中。
  • (3)如果可以进行位图扫描,则创建一条位图扫描访问路径。估计所有的位图扫描的代价,并将代价写入到路径中。然后将位图扫描路径添加到pathlist变量中。

3.从RelOptInfo的pathlist中,找出代价最小的访问路径。

4.如有必要,估计LIMIT,ORDER BY和AGGREGATE操作的代价。

例1:
首先来研究一个不带索引的简单单表查询;该查询同时包含WHERE和ORDER BY子句。

testdb=# \d tbl_1
     Table "public.tbl_1"
 Column |  Type   | Modifiers 
--------+---------+-----------
 id     | integer | 
 data   | integer | 

testdb=# SELECT * FROM tbl_1 WHERE id < 300 ORDER BY data;

在这里插入图片描述
(1)创建一个RelOptInfo结构,将其保存在PlannerInfo结构的simple_rel_array字段中。

(2)在RelOptInfo结构的baserestrictinfo字段中,添加一条WHERE子句。WHERE子句id<300会经由initsplan.c中定义的distribute_restrictinfo_to_rels()函数,添加至列表变量baserestrictinfo中。另外由于目标表上没有相关索引,RelOptInfo的indexlist字段为空。

(3)为了满足排序要求,planner.c中的standard_qp_callback()函数会在PlannerInfo的sor_pathkeys字段中添加一个pathkey。Pathkey是表示路径排序顺序的数据结构。本例因为查询包含一条ORDER BY子句,且该子句中的列为data,故data会被包装为pathkey,放入列表变量sort_pathkeys中。

(4)创建一个Path结构,并使用cost_seqscan函数估计顺序扫描的代价,并将代价写入Path中。然后使用pathnode.c中定义的add_path()函数,将该路径添加至RelOptInfo中。如之前所提到过的,Path中同时包含启动代价和总代价,都是由cost_seqscan函数所估计的。
在本例中,因为目标表上没有索引,计划器只估计了顺序扫描的代价,因此最小代价是自动确定的。

在这里插入图片描述

(5)创建一个新的RelOptInfo结构,用于处理ORDER BY子句。注意新的RelOptInfo没有baserestrictinfo字段,该信息已经被WHERE子句所持有。

(6)创建一个排序路径,并添加到新的RelOptInfo中;然后让SortPath的subpath字段指向顺序扫描的路径。

typedef struct SortPath
{
    Path    path;
    Path    *subpath;        /* 代表输入来源的子路径 */
} SortPath;

SortPath结构包含两个Path结构:path与subpath;path中存储了排序算子本身的相关信息,而subpath则指向之前得到的代价最小的路径。

注意顺序扫描路径中parent字段,该字段指向之前的RelOptInfo结构体(也就是在baserestrictinfo中存储着WHERE子句的那个RelOptInfo)。因此在下一步创建计划树的过程中,尽管新的RelOptInfo结构并未包含baserestrictinfo,但计划器可以创建一个包含Filter的顺序扫描节点,将WHERE子句作为过滤条件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值