005-sql语句执行流程解析3-查询物理优化

sql执行语句流程解析

整个处理流程在exec_simple_query函数中完成,代码架构如下:

/*
 * exec_simple_query
 *
 * Execute a "simple Query" protocol message.
 */
static void
exec_simple_query(const char *query_string)
{
	...
	//原始语法树获取
	/*
	 * Do basic parsing of the query or queries (this should be safe even if
	 * we are in aborted transaction state!)
	 */
	parsetree_list = pg_parse_query(query_string);

	...
	//循环处理sql语句
	/*
	 * Run through the raw parsetree(s) and process each one.
	 */
	foreach(parsetree_item, parsetree_list)
	{
		...
		
		//对原始语法树进行分析和重写,生成查询语法树
		querytree_list = pg_analyze_and_rewrite(parsetree, query_string,
												NULL, 0, NULL);
		//对查询语法树进行优化,生成执行计划
		plantree_list = pg_plan_queries(querytree_list,
										CURSOR_OPT_PARALLEL_OK, NULL);

		...
		
		//执行语句
		/*
		 * Run the portal to completion, and then drop it (and the receiver).
		 */
		(void) PortalRun(portal,
						 FETCH_ALL,
						 true,	/* always top level */
						 true,
						 receiver,
						 receiver,
						 completionTag);

		...
	}
	...
}

查询物理优化

什么是物理优化:在有效的查询路径中选择一条最优的路径

物理优化原理:减少查询访问路径中对元组的物理访问代价;因为物理I/O是当前数据库架构的主要瓶颈,所以物理优化的主要目的就是减少物理I/O操作。

物理优化代码实现:逻辑优化和物理优化的代码在query_planner函数中实现,先进行逻辑优化(详见05-sql语句执行流程解析2-查询逻辑优化),最后将逻辑优化的结果作为参数传递给make_one_rel函数做物理优化。一下主要对make_one_rel函数进行分析。

物理优化代码事项步骤:

- 1、查询系统物理参数
- 2、搜索所有可能的查询路径
- 3、选择最优路径	

make_one_rel函数树状图如下:

后续程序比较复杂,为了方便描述,使用树结构进行解析

make_one_rel
    |-- ...
    |-- set_base_rel_sizes //设置查询语句中每个基表的物理参数
        |-- loop循环:结构体root->simple_rel_array中获取基表信息
        |-- set_rel_size //根据基表类型对基本结构体进行填充,对基表类型进行大小估计
            |-- check_index_predicates //部分索引检查
            |-- set_baserel_size_estimates //物理参数估计:涉及到“选择率” 
            
    |-- set_base_rel_pathlists //设置每个基表的可行扫描方案
        |-- loop循环:结构体root->simple_rel_array中获取基表信息
        |-- set_rel_pathlist //对基表类型构建访问路径
            |-- set_plain_rel_pathlist //普通基表的处理
                |-- create_plain_partial_paths //顺序访问
                |-- create_index_paths //索引访问
                |-- create_tidscan_paths //TID 访问
            |-- ... //子查询,公共表达式等参考普通基表
    |-- make_rel_from_joinlist //创建所有可行方案的查询访问路径
        |-- draft.md
        |-- page.md
        |-- post.md
        |-- draft.md

set_base_rel_sizes 设置查询语句中每个基表的物理参数

check_index_predicates 部分索引检查

通过索引获取地址是比较常用的技术,在实际物理参数获取时,因为不需要生成全部索引(因为索引查询也需要访问I/O),所以引入部分索引---在源数据表中的部分数据上建立索引,由一组约束条件来约束所建立的索引数据的范围。

部分索引语法部分使用where指定约束条件。

set_baserel_size_estimates 判断约束条件的查询时间

这里涉及到数据查询约束条件导致的查询代价,所以需要知道满足约束条件的数据元组个数。

个数可以通过两个方式获取:

  • 全表扫描 ------ 会花费巨大的I/O代价,所以不是最小代价
  • 满足约束条件的元组比例 ------ 需要通过某个算法获取到元组比例,这里使用的“选择率”

选择率:描述一张表里某个数值得区分度。(因为存储的数据在变化,所以该值也在变化)

[选择率]:索引在全表中可能得出的区分度(随着存储数据变数,区分度也是变化的,这就是查询计划漂移,有人在注研究这个,这里不讨论)。因为全表扫描会大量访问I/O,所以创建索引时使用where添加判断语句,创建部分索引。

数值类型计算公式:

[Num(No.(x - 1)) + (N - X.min)/(X.max-X.min)] / NumOfBucket

Num(No.(x - 1)) :约束条件下满足的桶之前所有的桶数量

N:约束条件

X.max、X.min:约束条件下满足桶的上下边界

NumOfBucket:桶的总数

cpu_cost_function io_cost_function查询执行代价

最后会通过CPU和I/O的代价计算出最优或者次优的查询计划

set_base_rel_pathlists 设置每个基表的可行扫描方案

普通基表是子查询、公共表达式(CTE)等的基础,所以这里重点分析普通基表的访问路径构建方法。

基表的三种查询路径:顺序访问(Sequential scan)索引访问(Index scan)直接TID访问(TID scan)

由此可知:对基表的访问方式也是三种,set_base_rel_pathlists 函数中会对每种方式进行构建,以找到最优的访问方式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章