PostgreSQL执行路径生成
pg_plan_query 执行计划入口
planner 实际执行计划调用
这里提供一个planner_hook,以供自定义执行计划使用
standard_planner 默认planner hook
入口处,判断SQL是否是可以并行查询的
判断条件
(cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 && 当前游标允许并行查询
IsUnderPostmaster && 当前是直接调用的用户进程(postmaster的子进程),
parse->commandType == CMD_SELECT && 当前是select
!parse->hasModifyingCTE && 当前CTE是不可变集合
max_parallel_workers_per_gather > 0 && parallel worker 设置大于0
!IsParallelWorker() 当前不是worker的执行进程
之后判定 glob->parallelModeNeeded = glob->parallelModeOK &&
(force_parallel_mode != FORCE_PARALLEL_OFF);
如果启用了force_parallel_mode,但查询不支持并行查询,则依然不使用并行查询
如果使用了CURSOR_OPT_FAST_PLAN
处理 tuple_fraction
如果tuple_fraction>= 1 则设置为0
如果tuple_fraction <=0 设置为 1e-10;
否则 tuple_fraction=0
之后执行subquery_planner 获取执行计划
fetch_upper_rel 获取上层releation
get_cheapest_fractional_path 选取最佳执行路径
create_plan 创建执行计划
对于CURSOR_OPT_SCROLL
如果不支持后台执行ExecSupportsBackwardScan
则附加一个物化查询计划 materialize_finished_plan
对于可以并行的查询,这里处理gather进行可行性测试(不实际执行,但需要验证gather可以创建)
创建gather
执行列表
worker数量
并行执行计划
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;
执行代价计算
最后执行一系列赋值,完成plan
subquery_planner
子查询计划,所有查询都是子查询迭代,这个函数处理实际的plan
如果存在cte定义,则设置入SS_process_ctes
如果没有from定义,使用replace_empty_jointree设置一个空值
对于ANY以及 EXISTS ,执行pull_up_sublinks,也就是执行查询而非下推到子查询
对于可以预先处理的函数,执行inline_set_returning_functions 设置为inline状态
检查并合并所有可以合并的子查询pull_up_subqueries
对于union all,执行flatten_simple_union_all合并到append rel。
处理join的情况,逐个表进行迭代,根据rtekind进行判断
RTE_RELATION
如果设置了inh,检查是否为有子查询的项目,如果没有,清理inh标记
RTE_JOIN
如果是外查询,则设置外查询标记
RTE_RESULT
如果是结果,设置hasResultRTEs为true,这是执行计划全局设置
如果设了lateral,则hasLateralRTEs为true
最后设置安全查询级别
执行RowMark的前置任务 preprocess_rowmarks
如果设置了having 设置hasHavingQual标记
清理hasPseudoConstantQuals标记,
处理preprocess_expression表达式
对于hasTargetSRFs默认为true(函数会返回一组返回值的情况),通过expression_returns_set计算得到其实际是否存在
对于使用了WITH CHECK OPTION的情况,使用WithCheckOption处理所有表达式的qual到withCheckOptions
计算returningList的返回情况,确定其返回单值还是list
preprocess_qual_conditions逐个计算所有子查询的qual(约束)
preprocess_expression计算当前查询的havingQual
对于所有的windows调用
设置windows起点
设置window终点
设置limit的offset和count
对于设置了onConflict的情况
处理设定的可能冲突的参数,冲突where条件,set操作,以及set关联的where条件
初始化append_rel_list(根据当前append_rel_list预计算计算表达式)
处理所有子表的RTE表达式
RTE_RELATION 对象是表 设置为简单表查询
RTE_SUBQUERY 对象是子查询 处理子查询的别名问题
RTE_FUNCTION 对象是函数 直接调用preprocess_expression处理
RTE_TABLEFUNC 对象是表函数(列的列表)直接调用preprocess_expression处理
RTE_VALUES 对象是values列表,使用preprocess_expression处理
最后处理安全设置rte->securityQuals
对于存在hasJoinRTEs的情况,清理掉所有joinaliasvars(前面已经通过表达式预处理处理掉了别名的情况)
尝试处理having操作到where条件里面
对于
包含子查询
存在group set
存在函数计算
存在统计计算
的条件,不处理
对于没有group set的情况,处理having条件到where
其他条件having和where都复制一份
去掉不必要的grouping列 remove_useless_groupby_columns
reduce_outer_joins尝试去掉外查询,变成内联查询
remove_useless_result_rtes 尝试删除没有被用到的结果表达式
对于继承表执行inheritance_planner
默认执行grouping_planner
SS_identify_outer_params找到所需要外层传入的参数
SS_charge_for_initplans 修改init计划
重新计算initplan_cost: 每个子查询的initsubplan->startup_cost + initsubplan->per_call_cost;
当前path->startup_cost,path->total_cost都会加上initplan_cost
set_cheapest 最终确认当前是最优执行计划
逐个检查所有执行路径,比较选择出来最小代价path
grouping_planner 实际执行planner的内部
设置limit和offset,对于offset没有设置的话,默认为0
对于UNION/INTERSECT/EXCEPT
对于sortClause 存在sort的情况,设置tuple_fraction=0.0
plan_set_operations
使用最左(第一个)查询的列作为顺序
对于递归union
生成递归路径 generate_recursion_path ?
默认执行 recurse_set_operations?
检查是否可以并行is_parallel_safe(root, (Node *) final_target->exprs);
这里再次检查不允许使用for update,for share关键字(语法层已经检查,这里再检查一遍)
最后设置排序的顺序make_pathkeys_for_sortclauses
对于grouping设置了sets的情况
执行preprocess_grouping_sets预先处理
否则正常处理group语法 preprocess_groupclause
设置查询目标列表,方便上层使用 preprocess_targetlist
处理执行代价
get_agg_clause_costs
对于windows函数
find_window_functions找到所有函数,之后select_active_windows设置到activeWindows
对于max,min计算,执行preprocess_minmax_aggregates (这里代码与query_planner重复,如果需要变更min、max逻辑,则需要两边一起改)
确认是否有写死的limit_tuples以及非例外,如果有,采用写死的limit_tuples(例外为这些: group,group set,distinct,aggregation(聚合操作),windows函数,having条件,函数调用 设置root->limit_tuples = -1.0)
设置windows回调activeWindows以及group回调groupClause,包括rollups
执行query_planner standard_qp_callback 处理执行路径,并处理sort,distinct的情况
转换processed_tlist到final_target create_pathtarget
对于order by判断是否可以跳过排序阶段 make_sort_input_target
比如distinct,union的情况
对于windows函数,判断是否可以跳过group聚合操作 make_window_input_target
如果已经有预先的处理
对于having group(包括having,group,聚合函数,group set的情况)
规划是否需要计算
对于返回集合的函数 SRF set-returning functions
切割出来stfs split_pathtarget_at_srfs
final_target
sort_input_target
grouping_target
scanjoin_target
apply_scanjoin_target_to_paths 处理所有scan,join 表,并遍历其子代生成最终的扫描目标
保存对象信息
root->upper_targets[UPPERREL_FINAL] = final_target;
root->upper_targets[UPPERREL_ORDERED] = final_target;
root->upper_targets[UPPERREL_DISTINCT] = sort_input_target;
root->upper_targets[UPPERREL_WINDOW] = sort_input_target;
root->upper_targets[UPPERREL_GROUP_AGG] = grouping_target;
create_grouping_paths创建grouping路径,对于SRF单独处理adjust_paths_for_srfs
create_window_paths创建windows执行路径,对于SRF单独处理adjust_paths_for_srfs
create_distinct_paths创建distinct执行路径,对于srf单独处理adjust_paths_for_srfs
create_ordered_paths创建order by执行路径,对于srf单独处理adjust_paths_for_srfs
获取上层rel fetch_upper_rel
如果上层允许并行,则此处允许并行
final_rel->consider_parallel = true;
开始处理最终查询 遍历当前所有path
对于for update,share 创建create_lockrows_path
设置limit create_limit_path
对于非select(insert,update,delete)新增ModifyTable节点 parse->commandType != CMD_SELECT && !inheritance_update
使用索引
检查check option withCheckOptionLists
返回列表 returningLists
行标记(加锁) rowMarks
对于分区表单独标记 rootRelation
创建create_modifytable_path 新增ModifyTable节点
新增到执行path
处理partial add_partial_path
对于并行查询认为并不安全,单独处理
记录额外信息
extra.limit_needed = limit_needed(parse);
extra.limit_tuples = limit_tuples;
extra.count_est = count_est;
extra.offset_est = offset_est;
对于fdw
GetForeignUpperPaths 新增fdw执行路径
create_upper_paths_hook
最后执行其他hook
is_parallel_safe 并行是否安全
对于参数来源为本查询,或者上层查询的情况,认为可以并行查询
max_parallel_hazard_walker
调用函数是自己节点上的,安全
对于max min来说,并行查询是安全的
proparallel
PROPARALLEL_SAFE:false
PROPARALLEL_RESTRICTED 返回 context->max_interesting == proparallel
PROPARALLEL_UNSAFE false context->max_hazard = proparallel;
CoerceToDomain允许并行 domain类型不建议使用不可并行化的函数
对于获取序列下一个号的情况,认为不安全
windows函数认为是安全的
SubLink(any之类)安全 //目前应该不会有这个执行路径
对于子查询,只有安全的子查询才会被标记为可并行
参数来自本查询,认为安全,如果是不可控,认为不安全
for update、share 认为不安全
如果以上情况都未命中,迭代检查子查询,以及参数
apply_scanjoin_target_to_paths
处理所有scan,join 表,并遍历其子代生成最终的扫描目标
对于分区表,重新生成扫描对象
对于不可以并行的查询,关闭并行查询
逐个处理不涉及SRF的扫描
对于tlist_same_exprs(表达式应用于当前对象)
仅增加sort group 避免额外的执行计划创建代价
否则 create_projection_path
创建一个预测执行path,
对于partial进行同样处理
对于srf,逐个处理adjust_paths_for_srfs
设置当前对象为最终的scan/join目标(即便是SRF)
对于分区表
生成Append scan、join发生在append下而非其上,最终聚合到单独的result处理
逐个分区遍历
把每个分区的扫描路径设置为分区路径而非表路径
PathTarget *target = lfirst_node(PathTarget, lc);
target = copy_pathtarget(target);
附加扫描点到child_scanjoin_targets
apply_scanjoin_target_to_paths替换原先的执行路径
add_paths_to_append_rel新增执行路径
apply_scanjoin_target_to_paths此处向内迭代
live_children 仅处理实际存在的子分区(有可能是dummy对象)
对于可以并行的查询(对象不是外查询对象)
generate_gather_paths生成并行查询计划
create_gather_path生成simple_gather_path
对于每个path内的列表
生成create_gather_merge_path
最后重新选取代价最低的执行计划
set_cheapest
query_planner
处理执行路径
create_pathtarget
创建执行路径
get_agg_clause_costs 计算聚合代价
create_projection_path 创建执行预测path
make_sort_input_target 判断是否可以跳过sort
make_window_input_target 判断是否可以跳过grouping操作
最后修改时间:2019-12-09 16:45:53
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。