因爲SQL是聲明式語言(declarative),用戶只告訴了DBMS想要獲取什麼,但沒有指出如何計算。所以,DBMS須要將SQL語句轉換成可執行的查詢計劃(Query Plan)。可是對一樣的數據能夠有多種查詢方案,性能也差距很大,查詢優化器(Query Optimizer)的任務就是從給定的查詢中選擇一個最優的方案。算法
最先的查詢優化器實現是IBM在1970s設計的 System R,其中的概念和設計到如今依然有不少使用。對於查詢優化一般有兩種方案:sql
若是兩個關係代數表達式生成相同的元組集,則它們是等價的。DBMS 能夠在沒有成本模型的條件下生成更優的查詢計劃,即查詢重寫(Query Rewriting)數據庫
當應用程序向數據庫發送SQL查詢,DBMS首先要將SQL解析成語法樹的標記,Binder 查詢系統目錄將語法樹標記替換爲內部標識符,生成邏輯查詢計劃,最後由查詢優化器選擇最高效的執行方案。dom
一種等價的關係代數是謂詞下推,對於這樣的SQL查詢:性能
SELECT s.name, e.cid FROM student AS s, enrolled AS e WHERE s.sid = e.sid AND e.grade = 'A'
相比於先鏈接再過濾,應當更早地對數據進行過濾,以減小鏈接時的元素數量。優化
有關選擇(selection)的優化:spa
有關投影(projection)的優化:(列存儲無需進行這兩條優化)設計
有關鏈接(join)的優化:code
R⋈S = S⋈R,所以能夠重排多個表的鏈接順序blog
但對於n個表,不一樣的鏈接順序爲卡特蘭數(\(≈4^n\))
若是要對全部順序窮舉的話,當n較大時效率會很是低。鏈接順序一般由cost based search選擇最優/較優的方案。
SELECT ARTIST.NAME FROM ARTIST, APPEARS, ALBUM WHERE ARTIST.ID=APPEARS.ARTIST_ID AND APPEARS.ALBUM_ID=ALBUM.ID AND ALBUM.NAME="Andy's OG Remix"
對於這樣的SQL查詢,最樸素的查詢方案多是左圖所示,但經過:
能夠優化爲右圖所示的方案
其餘優化包括:
忽略沒必要要的join、projection
SELECT A1.* FROM A AS A1 JOIN A AS A2 ON A1.id = A2.id; //unnecessary SELECT * FROM A AS A1 WHERE EXISTS(SELECT val FROM A AS A2 //unnecessary WHERE A1.id = A2.id);
合併謂詞:
SELECT * FROM A WHERE val BETWEEN 1 AND 100 OR val BETWEEN 50 AND 150;
對於嵌套查詢,有兩種方案:
這種優化方式分爲兩個步驟:
首先要爲特定的執行計劃生成成本估算,而訪問磁盤的消耗始終是查詢中最主要的消耗,而且還要考慮順序訪問仍是隨機訪問,這二者在性能上也有極大差別。
選擇基數
DBMS 在目錄中存儲有關屬性、索引的內部信息。對於每一個關係R,DBMS維護如下信息:
則選擇基數(selection cardinality SC(A,R))爲給定屬性的值的平均數量 \(SC(A, R) = N_R / V(A,R)\)
在計算cost的時候,須要考慮不一樣謂詞選擇的範圍。謂詞的選擇性(selective)便是一個謂詞限定的部分。
好比對SC(A,R)=2的關係R中,若A的數據爲1-100的連續整數,則對於查詢
SELECT * FROM R WHERE A >50
能夠計算出\(sel(A>50) = 50/100 = 1/2\)
SELECT * FROM R WHERE A = 2 OR B LIKE 'A%'
\(sel(P1 ⋁ P2) = sel(P1) + sel(P2) – sel(P1⋀P2) = sel(P1) + sel(P2) – sel(P1) ∙ sel(P2)\)
也能夠說,選擇性就是指這個範圍的數據出現的機率。
但以上的估計基於三個假設:
所以得出的結果是一個估計值,並不精確。
統計直方圖
能夠對第一個假設進行優化,在每一個表中儲存有關數據的直方圖,將數據按範圍進行統計,在計算sel時從直方圖中計算相應的比例。
樣本估算
現代DBMS從表中選擇必定的樣本估算sel,當底層表發生顯著變化時更新樣本。
對於簡單的單表查詢(OLTP),經過啓發式規則,利用索引和二分搜索足以獲取良好的性能。
可是對於OLAP中的多表查詢,不一樣的鏈接順序會對性能形成很大影響。而因爲關係的增長會致使可選擇方案指數增加(\(4^n\)),所以須要約束可選擇的空間。
System R 中只考慮左深鏈接樹(Left Deep Join),將選擇空間縮小到 \(n!\),但現代DBMS中再也不總作出這樣的假設。
左深鏈接樹即鏈接的右表必定爲一個基本表,經過流水線鏈接,中間結果不寫入臨時文件。
對於鏈接,須要考慮鏈接的順序,不一樣表之間鏈接的方式(Hash join, Sort-Merge join),獲取數據的方式。經過動態規劃對方案進行剪枝。
除了經過動態規劃剪枝以外,當鏈接表過多時,會選擇一些局部最優解的方式:
greedy join enumeration algorithm
在每次循環中,選擇使總代價最低的方案
Randomized algorithm
隨機重寫查詢方案,利用模擬退火等算法進行優化
Genetic algorithm(遺傳算法)
經過鏈接方案(結合子代)和隨機突變進行優化
至於爲何是左深鏈接樹,而不是右深鏈接樹?動態規劃的執行優化又是如何實現的?留待後面分析