時間一晃週末就過完了,時間過得太快,不禁得讓人倍加珍惜。時間真是不夠用哈~node
好的不廢話,此次咱們開始看查詢規劃模塊的源碼吧。數據庫
查詢規劃部分的在整個查詢處理模塊應該是在一個很是重要的地位上,這一步直接決定了查詢的方式與路徑,很大程度上影響了數據庫查詢的查詢性能。所以這一塊代碼量也很大,我也會花較多的筆墨來分析這個模塊的代碼。在篇幅上,可能查詢規劃這一模塊我會用2到3篇文章來細細的說明下。今天這一篇先整體概述下查詢規劃模塊的全貌,在介紹該模塊的一個重要的子模塊(總共三個主要模塊)就結束吧,剩下的交給第二篇吧。express
廢話很少說,我先上圖。下圖大概的刻畫了查詢規劃模塊裏主要的函數調用關係,固然啦,最下層只畫到主處理函數,主處理函數內部的調用關係在介紹每一個主處理函數的時候再細細的說吧。app
我簡單介紹下吧。exec_simple_query函數是負責查詢處理的主函數,在進行查詢規劃以前,它已經調用了查詢分析和查詢重寫模塊。還記得上一篇博文的查詢重寫模塊麼?該模塊返回的是重寫後的查詢樹鏈表。這裏exec_simple_query函數將重寫後的查詢樹鏈表交給查詢規劃模塊進一步處理。查詢規劃模塊的入口函數是pg_plan_queries函數。ide
pg_plan_queries函數調用pg_plan_query函數對每個查詢進行處理並返回PlannedStmt(執行計劃)結構體鏈表。這裏要注意,查詢規劃模塊只會對非UTILITY命令進行處理。函數
而在pg_plan_query函數裏,實際是調用planner函數負責查詢計劃的生成。性能
planner函數調用standard_planner函數進入標準的查詢規劃處理流程。該函數接受查詢樹以及相關參數,返回PlannedStmt結構體,詳細的結構以下:優化
typedef struct PlannedStmt { NodeTag type; CmdType commandType; /* select|insert|update|delete */ uint32 queryId; /* query identifier (copied from Query) */ bool hasReturning; /* is it insert|update|delete RETURNING? */ bool hasModifyingCTE; /* has insert|update|delete in WITH? */ bool canSetTag; /* do I set the command result tag? */ bool transientPlan; /* redo plan when TransactionXmin changes? */ struct Plan *planTree; /* tree of Plan nodes */ List *rtable; /* list of RangeTblEntry nodes */ /* rtable indexes of target relations for INSERT/UPDATE/DELETE */ List *resultRelations; /* integer list of RT indexes, or NIL */ Node *utilityStmt; /* non-null if this is DECLARE CURSOR */ List *subplans; /* Plan trees for SubPlan expressions */ Bitmapset *rewindPlanIDs; /* indices of subplans that require REWIND */ List *rowMarks; /* a list of PlanRowMark's */ List *relationOids; /* OIDs of relations the plan depends on */ List *invalItems; /* other dependencies, as PlanInvalItems */ int nParamExec; /* number of PARAM_EXEC Params used */ bool hasRowSecurity; /* row security applied? */ } PlannedStmt;
該結構體裏包含了後續的查詢執行模塊所須要的所有信息,包括計劃樹(Plan)、子計劃樹(SubPlan)和其餘重要的參數信息。ui
在standard_planner函數裏,主要經過subquery_planner函數和set_plan_references函數分別完成查詢計劃的處理優化和清理工做。this
set_plan_references函數這裏很少作介紹,這不是查詢計劃的重點,咱們把重點放在subquery_planner函數上。該函數接收Query(查詢樹),返回一個Plan(計劃樹)。
如圖所示,咱們能夠看出subquery_planner函數的處理主要分爲三部分。
主要調用相關預處理函數,依據消除冗餘條件、減小遞歸層次和簡化路徑等原則對查詢樹進行預處理。主要包括:
此處主要調用grouping_planner函數來處理(inheritance_planner函數在處理完繼承關係後仍然調用的是grouping_planner函數來處理)。在執行過程當中,該函數再也不對查詢樹作變換處理,而是將查詢中的信息進行規範化並傳給query_planner函數。query_planner函數調用make_one_rel函數進入查詢優化階段,並將最優路徑放入cheapest_path。而後grouping_planner函數再調用get_cheapest_fractional_path_for_pathkeys函數尋找符合需求的排序路徑sorted_path,並最終肯定最優路徑best_path並生成基本計劃樹(調用create_plan函數)。
調用set_plan_references函數清理現場,作一些變量的調整工做,不對計劃樹作本質的改變。
那麼,接下來的部分和後續文章(若是有的話)就從這三個部分展開了。
在進行正式的工做以前,經常須要作一些準備工做。在查詢規劃模塊尤爲是這樣,由於它的預處理階段作的事情仍是蠻多的,所以值得單獨拿出一節來說,另外從篇幅上考慮,講完預處理這篇就差很少了,由於以後的查詢規劃主過程的內容只會更多,應該要單獨拿出(至少)一篇講了。
查詢規劃模塊的預處理工做主要是提高子連接和子查詢,預處理表達式和having子句等。處理的對象主要是查詢樹中的範圍表rtable和鏈接樹jointree.
首先咱們先說明下子連接和子查詢的區別:子查詢是一條完整的查詢語句,而子連接是一條表達式,可是表達式內部也能夠包含查詢語句。直白點說呢就是:子查詢是放在FROM子句裏的而子連接則出如今WHERE子句或者HAVING子句中。
在subquery_planner函數裏,調用pull_up_sublinks函數處理WHERE子句和JOIN/ON子句中的ANY和EXISTS類型的子連接。
在用pull_up_sublinks函數內部,調用pull_up_sublinks_jointree_recurse函數遞歸地處理鏈接樹jointree:
1.對於RangeTblRef類型,直接返回; 2.對於FromExpr類型,遞歸調用pull_up_sublinks_jointree_recurse函數處理每一個節點並調用pull_up_sublinks_qual_recurse函數處理約束條件; 3.對於JoinExpr類型,遞歸調用pull_up_sublinks_jointree_recurse函數處理左右子樹並調用pull_up_sublinks_qual_recurse函數處理約束條件.
以後,subquery_planner函數調用pull_up_subqueries函數來提高子查詢。當子查詢僅僅是一個簡單的掃描或者鏈接時,就會把子查詢或者子查詢的一部分合併到父查詢中以進行優化。通常會分一下三種狀況處理:
1.在範圍表中存在子查詢。對於簡單的子查詢,直接調用pull_up_simple_subquery函數進行提高;而對於簡單的UNION ALL子查詢,調用pull_up_simple_union_all函數進行提高,其餘的狀況則不處理; 2.在FROM表達式中存在子查詢。對於FROM列表中的每一個節點都調用pull_up_subqueries遞歸處理; 3.鏈接表達式中的子查詢。調用pull_up_subqueries函數遞歸地處理.
這些話說的很頭疼,你們看了估計也頭疼。咱們舉一個例子好了,假設咱們有這樣一個SQL語句:
SELECT D.dname FROM dept D WHERE D.deptno IN (SELECT E.deptno FROM emp E WHERE E.sal = 100);
從字面上看,若是該語句中的子查詢被獨立地規劃,也就是說對於表dept中的每個元組deptno值,都要搜索一遍emp表。顯然這樣的作法很蠢,代價也很是大。可是若是咱們把子查詢提高併合併到父查詢中,那麼咱們看看效果。
先作提高子連接:
SELECT D.dname FROM dept D , (SELECT E.deptno FROM emp E WHERE E.sal = 100) AS Sub WHERE D.deptno = Sub.deptno;
而後再作提高子查詢:
SELECT D.dname FROM dept D ,emp E WHERE D.deptno = E.deptno and E.sal = 100;
能夠看到,這樣操做之後的SQL語句只要先作一下過濾(E.sal = 100),而後再把結果和dept表作一下鏈接便可,大大提升了查詢效率。
表達式的預處理工做主要由函數preprocess_expression完成。該函數採用遞歸掃描的方式處理PlannerInfo結構體裏面保存的目標屬性、HAVING子句、OFFSET子句、LIMIT子句和鏈接樹jointree。整體來講,作了如下幾件事:
1.調用flatten_join_alias_vars函數,用基本關係變量取代鏈接別名變量; 2.調用函數eval_const_expression進行常量表達式的簡化,也就是直接計算出常量表達式的值。例如:"3+1 <> 4" 這種會直接被替換成「FALSE」; 3.調用canonicalize_qual函數對錶達式進行規範化,主要是將表達式轉換爲最佳析取範式或者合取範式(這個博主也不太懂啊,尷尬); 4.調用函數make_subplan將子連接轉換爲子計劃.
對於函數make_plan,仍是要細細的說一下,其執行步驟以下:
1)首先複製子連接SubLink中的查詢樹Query,若是查詢樹是一個EXISTS型的子計劃,那麼則調用simplify_EXISTS_query函數對QUery副本進行處理; 2)調用subquery_planner函數爲子連接生成計劃,同時設置好參數tuple_fraction來告訴底層規劃器須要去多少個元組。對於EXISTS型的子查詢,設置該值爲1.0,取回一個元組便可;對於ALL和ANY型的子查詢,從機率上說大約取到50%的元組就能夠獲得結果,所以設置參數值爲0.5;對於其餘的查詢,統一設置爲0; 3)調用build_subplan函數將上一步生成的計劃轉換爲SubPlan或者InitPlan的形式,並將當前查詢層次的參數列表傳遞給它的子計劃; 4)對於生成的是SubPlan並且是一個簡單的EXISTS類型的查詢,調用convert_EXISTS_to_ANY函數嘗試將其轉換爲一個ANY類型的查詢並建立查詢計劃.
對於HAVING子句來講,除了進行前面所提到的預處理外,還須要處理其中的每一個條件。若是HAVING子句中沒有彙集函數的話,那麼它徹底能夠退化到WHERE子句中去,不然的話他將被寫到查詢樹的HavingQual字段裏面。
具體來講的話:
step1:初始化一個空的HAVING子句鏈表newHaving; step2:掃描HAVING子句鏈表中的每一條HAVING子句: 1.若是HAVING子句中存在彙集函數、易失函數或者子計劃中的一種或多種,則添加至newHaving鏈表中; 2.若是不包含GROUP子句,則添加至WHERE子句中; 3.其餘狀況,將這條HAVING子句添加至WHERE子句同時也保存至newHaving中. step3:用處理後的HAVING、子句鏈表newHaving替換原來的HAVING子句鏈表.
舉個例子,例若有以下SQL語句:
SELECT a FROM b WHERE a.c = 10 HAVING a.d > 5;
在HAVING子句裏沒有彙集,那麼能夠直接將"a.d > 5" 提高到WHERE子句中,即:
SELECT a FROM b WHERE a.c = 10 AND a.d > 5;
因此這一篇就又這樣水完了?不甘心啊。感受寫的蠻糟的。此次大概就把查詢規劃模塊的整體和預處理階段簡單介紹了下,感受思路有點亂,有空再捋捋。後面一篇再細細的說說查詢規劃的主處理過程吧~感受是塊硬骨頭,很差啃。
好的,你們下期見~