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