查詢優化是數據庫管理系統中承上啟下的一個模塊,它接收來自語法分析模塊傳遞過來的查詢樹,在這個查詢樹的基礎上進行了邏輯上的等價變換、物理執行路徑的篩選,並且把選擇出的最優的執行路徑傳遞給數據庫的執行器模塊。查詢優化器的輸入是查詢樹,輸出是查詢執行計划。
- 查詢優化器和數據庫用戶之間的信息不對稱,查詢優化器在優化的過程中會參考數據庫統計模塊自動產生的統計信息,這些統計信息從各個角度來描述數據的分布情況,查詢優化器會綜合考慮統計信息中的各種數據從而得到一個好的執行方案,而數據庫用戶一方面無法全面地了解數據的分布情況,另一方面即使數據庫用戶獲得了所有的統計數據,人腦也很難構建一個精確的代價計算模型來對執行方案進行篩選。
- 查詢優化器和數據庫用戶之間的時效性不同,數據庫中的數據瞬息萬變,一個在A時間點執行性能很高的執行計划,在B時間點由於數據內容發生了變化,它的性能可能就很低,查詢優化器則隨時都能根據數據的變化調整執行計划,而數據庫用戶則只能手動更改執行方案,和查詢優化器相比,它的時效性比較低。
- 查詢優化器和數據庫用戶之間的計算能力不同,目前計算機的計算能力已經大幅提高,在執行數值計算方面和人腦相比具有巨大的優勢,查詢優化器對一個語句進行優化時,可以從幾百種執行方案選出一個最優的方案,而人腦要全面地計算這幾百種方案,需要的時間遠遠要長於計算機。
通常數據庫的查詢優化方法分為兩個層次:基於規則的查詢優化(邏輯優化,Rule Based Optimization,RBO);基於代價的查詢優化(物理優化,Cost Based Optimization,CBO)。邏輯優化是建立在關系代數基礎上的優化,關系代數中有一些等價的邏輯變換規則,通過對關系代數表達式進行邏輯上的等價變換,可能會獲得執行性能比較好的等式,這樣就能提高查詢性能;而物理優化則是在建立物理執行路徑的過程中進行優化,關系代數中雖然指定了兩個關系如何進行連接操作,但是這時的連接操作符屬於邏輯運算符,它沒有指定以何種方式實現這種邏輯連接操作,而查詢執行器是不認識關系代數中的邏輯連接操作的,需要生成多個物理連接路徑來實現關系代數中的邏輯連接操作,並且根據查詢執行器的執行步驟,建立代價計算模型,通過計算所有的物理連接路徑的代價,從中選擇出最優的路徑。
文件介紹
PostgreSQL數據庫的查詢優化的代碼在src/backend/optimizer目錄下,其中有plan、prep、path、geqo、util共5個子目錄,plan是總入口目錄,它調用了prep目錄進行邏輯優化,調用path、geqo目錄進行物理優化,util目錄是一些公共函數,供所有目錄使用。在執行中,從Plan模塊入口,先調用Prep模塊進行預處理,再調用Path模塊進行優化。Path模塊中有開關,指示是否啟用遺傳算法進行優化,如果啟用,且連接的表超過11,就調用geqo目錄中的遺傳算法進行優化。
prep目錄主要處理邏輯優化中的邏輯重寫的部分,對投影、選擇條件、集合操作、連接操作都進行了重寫。
path目錄則主要是生成物理路徑的部分,包括生成掃描路徑、連接路徑等。
geqo目錄主要是實現了一種物理路徑的搜索算法——遺傳算法,通過這種算法可以處理參與連接的表比較多的情況。
查詢樹
PostgreSQL數據庫中的結構體采用了統一的形式,它們都是基於Node結構體進行的“擴展”,Node結構體中只包含一個NodeTag成員變量,NodeTag是enum(枚舉)類型。
1 typedef struct Node{ 2 NodeTag type; 3 } Node;
其他的結構體則利用C語言的特性對Node結構體進行擴展,所有結構體的第一個成員變量也是NodeTag枚舉類型,例如在List結構體里,第一個成員變量是NodeTag,它可能的值是T_List、T_intList或T_OidList,這樣就能分別指代不同類型的List。
1 typedef struct List{ 2 NodeTag type; // T_List, T_IntList, T_OidList 3 int length; 4 ListCell *head; 5 ListCell *tail; 6 } List;
Query結構體以NodeTag枚舉類型作為第一個變量,它的取值為T_Query
1 typedef struct Query{ 2 NodeTag type; 3 CmdType commandType; // select | insert | update | delete | utility 4 QuerySource querySource; // where did I come from? 5 ...... 6 } Query;
無論是List結構體的指針,還是Query結構體的指針,都能通過Node結構體的指針(Node*)來表示,而在使用對應的結構體時,則通過查看Node類型的指針中的NodeTag枚舉類型就可以區分出該Node指針所代表的結構體的實際類型。
Query結構體
Query結構體是查詢優化模塊的輸入參數,其源自於語法分析模塊,一個SQL語句在執行過程中,經過詞法分析、語法分析和語義分析之后,會生成一顆查詢樹,PostgreSQL用Query結構體來表示查詢樹。查詢優化模塊在獲取到查詢樹之后,開始對查詢樹進行邏輯優化,也就是對查詢樹進行等價變換,將其重寫成一棵新的查詢樹,這個新的查詢樹又作為物理優化的輸入參數,進行物理優化。
commandType:查詢樹對應命令的類型,它是一個枚舉類型, 說明由哪類命令生成該查詢樹,包括以下幾類:CMD_SELECT、CMD_INSERT、CMD_UPDATE、CMD_DELETE和CMD_UTILITY。如果命令類型為CMD_UTILITY,則查詢優化器不會對該查詢樹進行優化。
querySource:是原始查詢還是來自於規則的查詢(被規則取代)
canSetTag:查詢重寫時用到,如果該Query是由原始查詢轉換而來則此字段為假,如果Query是由查詢重寫或查詢規划時新增加的則此字段為真
resultRelation:結果關系,是涉及數據修改的范圍表(實際使用的是范圍表的編號)。
List *rtable --> SQL是語句涉及的表清單,在查詢中FROM子句后面會指出需要進行查詢的范圍表,可能是對單個范圍表進行查詢,也可能是對幾個范圍表做連接操作,rtable中則記錄了這些范圍表。rtable是一個List指針,所有要查詢的范圍表就記錄在這個List中,每個表以RangeTblEntry結構體來表示,因此rtable是一個以RangeTblEntry結構體來表示,因此rtable是一個以RangeTbleEntry為節點的List鏈表。這個字段只適用於INSERT/UPDATE/DELETE命令,表示這些命令中需要更改的表或視圖。
targetList -->目標屬性,用於存放查詢結果屬性的表達式。目標屬性里的每個元素都包含一個表達式。它可以為常量值、某個范圍表的一個屬性、參數或者由函數調用、常量、變量、操作符等構成的表達式樹。分以下四種情況:SELECT語句目標屬性是位於SELECT和FROM之間的表達式;DELETE語句不需要目標屬性,因為DELETE語句不返回任何元組;INSERT語句目標屬性描述插入到結果關系的元組的屬性,這些屬性包括表名后指定的要插入值的屬性或INSERT...SELECT語句中SELECT子句里的表達式,查詢重寫過程的第一步就是為查詢中缺省字段增加目標屬性,剩下的字段(既無指定值也無缺省值)將會賦予常量NULL;UPDATE目標屬性描述被更新的屬性,即SET子句中的屬性。
jointree -->連接樹,查詢的連接樹顯示了FROM子句中表的連接情況。對於類似於SELECT...FROM a,b,c的簡單查詢,連接樹只是FROM中表的簡單列表,因為允許以任意順序連接這些表。如果使用JOIN表達式(尤其是外連接),必須按照該連接指定的順序進行連接。此時,連接樹顯示了JOIN表達式的結構。JOIN子句上的約束條件(ON或USING表達式)作為附加在連接樹節點上的條件表達式處理。通常,還會把頂層WHERE表達式作為附加在頂層連接樹節點上的條件表達式來處理,因此,連接樹實際上表示了SELECT語句中的FROM和WHERE子句。
JoinExpr結構體
RangeTblEntry結構體
TargetEntry結構體
VAR結構體
Var結構體表示查詢中涉及的表的列屬性,在SQL語句中,投影的列屬性、約束條件中的列屬性都是通過Var來表示的,在語法分析階段會將列屬性用ColumnRef結構體來表示,在語義分析階段會將語法樹中的ColumnRef替換成Var用來表示一個列屬性。varno用來確定列屬性所在的表的“編號”,這個編號源自Query(查詢樹)中的rtable成員變量,查詢語句中涉及的每個表都會記錄在rtable中,查詢