暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

PostgreSQL代码走读——query_planner

原创 chirpyli 2025-06-13
114

query_planner函数,生成一个基本查询的路径(可能包含Join),但是不涉及更高级的特性。因query_planner不处理顶层的操作(如分组、排序等),所以它无法自行选择最佳路径。因此,该函数会返回顶层连接操作对应的RelOptInfo结构,上层调用者grouping_planner可以从此关系的已知路径中选择最佳路径。

/* * query_planner - 查询规划器 * 生成基本查询的执行路径(即简化后的执行计划),该查询可能包含连接操作, * 但不处理分组、排序等高级特性。 * * 由于 query_planner 不处理顶层逻辑(如分组、排序等),它无法自行选择最优路径。 * 该函数返回顶层的连接关系 RelOptInfo,由调用方 grouping_planner * 从该关系中存活的路径中选择最终方案。 * * 参数说明: * root - 描述待规划查询的 PlannerInfo 结构体 * qp_callback - 用于安全时计算 query_pathkeys 的回调函数 * qp_extra - 传递给 qp_callback 的额外数据(可选) * * 注意: * PlannerInfo 结构体包含 query_pathkeys 字段,该字段指示最终输出计划 * 所需的排序顺序。此值在函数调用时不可用,需在完成等价类(equivalence classes) * 合并后,通过 qp_callback 计算得出(在等价类合并完成前无法构造规范化路径键)。 */ RelOptInfo *query_planner(PlannerInfo *root, query_pathkeys_callback qp_callback, void *qp_extra) { Query *parse = root->parse; List *joinlist; RelOptInfo *final_rel; /* Init planner lists to empty.. */ root->join_rel_list = NIL; /* 初始化基表数组 * 根据查询的范围表(rtable)长度决定数组大小,加1是为了让数组索引直接匹配范围表条目(RTE)的 rtindex(范围表索引从1开始) */ setup_simple_rel_arrays(root); /* 如果连接树(jointree)是单个 RTE_RESULT 类型的关系(最简场景), * 则直接绕过本函数的后续所有处理流程,仅创建对应的 RelOptInfo 结构 * 及其唯一的访问路径。这种优化值得单独处理,因为它适用于常见场景, * 例如 "SELECT 表达式" 或 "INSERT ... VALUES()" 这类简单查询。*/ if (list_length(parse->jointree->fromlist) == 1) { Node *jtnode = (Node *) linitial(parse->jointree->fromlist); if (IsA(jtnode, RangeTblRef)) { int varno = ((RangeTblRef *) jtnode)->rtindex; RangeTblEntry *rte = root->simple_rte_array[varno]; if (rte->rtekind == RTE_RESULT) { /* Make the RelOptInfo for it directly */ final_rel = build_simple_rel(root, varno, NULL); /* 如果查询总体允许并行处理,则需检查条件表达式(quals)是否并行受限。 * (此处无需检查 final_rel->reltarget,因为此时它为空。查询目标列表(tlist) * 中的任何并行受限内容将在后续处理。)通常情况下这看似多余,因为仅包含 Result * 的执行计划本身并无并行化意义。然而,若启用了 force_parallel_mode 模式, * 我们期望尽可能在并行工作进程中执行Result,因此必须执行此检查。*/ if (root->glob->parallelModeOK && force_parallel_mode != FORCE_PARALLEL_OFF) final_rel->consider_parallel = is_parallel_safe(root, parse->jointree->quals); /* 此关系的唯一路径是一个简单的Result路径。这里我们通过使用 GroupResultPath * 略微"取巧",因为这种方式可以直接将条件子句(quals)塞入路径中,而无需 * 预处理它们。(但若换一种视角,无 FROM 子句的 SELECT 本质上是一种退化分组场景, * 因此这种"取巧"并不算太过分。)*/ add_path(final_rel, (Path *) create_group_result_path(root, final_rel, final_rel->reltarget, (List *) parse->jointree->quals)); /* 从RelOptInfo的所有路径中找出成本最低的路径,并将其保存到关系体的 cheapest-path 相关字段中 */ set_cheapest(final_rel); /* 我们无需实际执行 generate_base_implied_equalities 函数, * 但需在逻辑上模拟等价类合并(EC merging)已完成的状态 */ root->ec_merging_done = true; /* 仍然需要调用 qp_callback 函数, * 例如在类似 "SELECT 2+2 ORDER BY 1" 这种包含排序但无表依赖的查询场景中。*/ (*qp_callback) (root, qp_extra); return final_rel; } } } /* 为查询中使用的所有基关系(base relations)创建 RelOptInfo 节点。 * 附加关系成员("其他关系")将在后续步骤中添加。 * * 注意:我们通过遍历连接树(jointree)而非扫描范围表(rangetable) * 来查找基关系的原因是:范围表中可能包含那些并非实际参与查询的关系的 RTE, * 例如视图。我们无需为这些关系创建 RelOptInfo */ add_base_rels_to_query(root, (Node *) parse->jointree); /* * 检查目标列(targetlist)与连接树(join tree),执行以下操作: * - 将所有被引用的变量(Vars)条目添加至基关系(baserel)的目标列 * - 为所有被引用的占位符变量(PlaceHolderVars)生成 PlaceHolderInfo 条目 * - 将约束条件(Restrict)与连接子句(join clauses)添加至相关关系的对应列表中 * - 为可证明等价的表达式构建等价类(EquivalenceClasses) * - 构建 SpecialJoinInfo 列表以存储连接顺序限制信息 * - 最终生成供 make_one_rel() 使用的目标连接列表(joinlist) */ build_base_rel_tlists(root, root->processed_tlist); /* * find_placeholders_in_jointree - 在连接树(jointree)中查找占位符变量(PlaceHolderVars)并构建占位符信息结构(PlaceHolderInfos) * * 此处无需检查目标列(targetlist),因为 build_base_rel_tlists() * 已为目标列中的所有 PHV(占位符变量)创建了对应的条目。 * * 本函数需在解构连接树(deconstruct_jointree)流程开始前调用。 * 一旦开始解构连接树,所有活跃的占位符必须存在于 root->placeholder_list 中, * 因为 make_outerjoininfo 和 update_placeholder_eval_levels 函数 * 在处理连接树向上遍历时需依赖这些信息。 */ find_placeholders_in_jointree(root); /* 处理 LATERAL 子查询的横向引用,为每个 LATERAL 子查询执行以下操作: * - 提取其所有对当前查询层级的变量(Vars)和占位符变量(PlaceHolderVars)的引用 * - 确保这些值在子查询求值时可用 */ find_lateral_references(root); /* * deconstruct_jointree - 解构连接树 * 递归扫描查询连接树中的 WHERE 和 JOIN/ON 条件子句,将其添加至基础 RelOptInfo 结构 * 的 restrictinfo 和 joininfo 列表。同时,为查询树中的外连接创建 SpecialJoinInfo * 节点并存入 root->join_info_list。最终返回一个供 make_one_rel() 制定连接顺序的 * "joinlist" 数据结构。 * * 返回值说明: * - "joinlist" 是由 RangeTblRef 节点或子连接列表(sub-joinlists)组成的层次结构 * - 同一层级的元素必须通过 make_one_rel() 确定连接顺序(可能受 SpecialJoinInfo 约束) * - 子连接列表表示需独立优化的子问题,当前仅由以下情况产生: * a) FULL OUTER JOIN 操作 * b) join_collapse_limit 或 from_collapse_limit 参数限制子问题合并 * * 处理策略说明: * - 内连接:条件子句可在所有涉及变量可用的最低层级求值 * - 外连接:禁止将条件下推至可空侧(nullable side),因为可能误生成 NULL 行。为此: * a) 通过逻辑或(OR)将外连接的最小关系集合并入上层子句的 required_relids * b) 强制延迟这些子句到外连接层(或更高层级)执行 */ joinlist = deconstruct_jointree(root); /* 现在我们已经建立了等价类,重新评估之前被推迟处理的所有外连接条件。 *(这可能会导致等价类的进一步添加或合并。)*/ reconsider_outer_join_clauses(root); /* 若已形成等价类,则按需生成额外限制子句(隐含连接子句将在后续流程中动态生成 */ generate_base_implied_equalities(root); /* 等价类合并已完成,现可生成规范形式的路径键,因此需计算PlannerInfo中的query_pathkeys及其他路径键相关字段 */ (*qp_callback) (root, qp_extra); /* 检查子查询上拉过程中生成的所有"占位符"表达式, * 确保其依赖的变量在对应连接层次被正确标记为必需。该处理需在连接移除前完成, * 因此类表达式可能导致上层连接需要原本未被标记的变量或占位符 */ fix_placeholder_input_needed_levels(root); /* 移除所有无效外连接。理想情况下,该操作应在连接树预处理阶段完成, * 但必需的信息需待基表相关数据结构构建完成且条件子句分类后才能获取 */ joinlist = remove_useless_joins(root, joinlist); /* 同时,将内表具有唯一性的半连接(semijoin)降级为普通内连接。 * 同理,该优化因需依赖前期信息采集,直至当前阶段方可执行 */ reduce_unique_semijoins(root); /* 当前需将"占位符"按需分配到基表。此操作必须在连接移除后进行, * 因为移除操作可能改变占位符在基表层面的可评估性 */ add_placeholders_to_base_rels(root); /* 在占位符变量评估层级最终确定后,构建横向引用集合 */ create_lateral_join_info(root); /* 将外键约束与等价类及连接条件进行匹配。该操作需在等价类最终确定后执行, * 且在连接移除完成后处理更具优势,可跳过涉及已移除关系的外键处理 */ match_foreign_keys_to_quals(root); /* 寻找可从连接OR子句中提取单表限制性OR子句的候选条件 */ extract_restriction_or_clauses(root); /* 当前阶段通过为附加关系(appendrels)的子表添加"子关系(otherrels)"完成扩展。 * 此处理被延迟至优化末期,以便尽可能收集基表关系的完整信息(包括所有限制条件),* 从而实现对不满足限制条件的分区进行剪枝。 * 同时注意,部分信息(如lateral_relids横向引用关系ID)需从基表传播至子关系, * 因此必须确保这些信息已预先计算完毕 */ add_other_rels_to_query(root); /* 将UPDATE/DELETE操作的行标识变量分发至目标关系。此操作需在完成附加关系(appendrels)扩展后执行 */ distribute_row_identity_vars(root); /* 查找查询执行的所有可能访问路径,返回代表所有基表连接的单一关系节点 */ final_rel = make_one_rel(root, joinlist); /* 检查至少有一个可用路径 */ if (!final_rel || !final_rel->cheapest_total_path || final_rel->cheapest_total_path->param_info != NULL) elog(ERROR, "failed to construct the join relation"); return final_rel; }
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

文章被以下合辑收录

评论