暂无图片
暂无图片
2
暂无图片
暂无图片
暂无图片

PostgreSQL执行路径生成的代码阅读笔记

原创 llzx373 2019-12-09
2268

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
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
2人已赞赏
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论