一、概述
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子句作为过滤条件。