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 函數中會對每種方式進行構建,以找到最優的訪問方式。

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