Database | 淺談Query Optimization (1)

綜述

因爲SQL是聲明式語言(declarative),用戶只告訴了DBMS想要獲取什麼,但沒有指出如何計算。所以,DBMS須要將SQL語句轉換成可執行的查詢計劃(Query Plan)。可是對一樣的數據能夠有多種查詢方案,性能也差距很大,查詢優化器(Query Optimizer)的任務就是從給定的查詢中選擇一個最優的方案。算法

最先的查詢優化器實現是IBM在1970s設計的 System R,其中的概念和設計到如今依然有不少使用。對於查詢優化一般有兩種方案:sql

  1. 基於啓發式規則:啓發式優化將查詢的部分與已知的模式進行匹配,以重組計劃。這些規則對查詢進行轉換,消除低效率的部分,這種方式不須要檢查數據自己。
  2. 基於代價的搜索:須要讀取數據並估計執行計劃的成本。而後從各個計劃中選擇成本最低的方案。

等價的關係代數(啓發式規則)

若是兩個關係代數表達式生成相同的元組集,則它們是等價的。DBMS 能夠在沒有成本模型的條件下生成更優的查詢計劃,即查詢重寫(Query Rewriting)數據庫

當應用程序向數據庫發送SQL查詢,DBMS首先要將SQL解析成語法樹的標記,Binder 查詢系統目錄將語法樹標記替換爲內部標識符,生成邏輯查詢計劃,最後由查詢優化器選擇最高效的執行方案。dom

image

一種等價的關係代數是謂詞下推,對於這樣的SQL查詢:性能

SELECT s.name, e.cid
FROM student AS s, enrolled AS e
WHERE s.sid = e.sid
AND e.grade = 'A'

相比於先鏈接再過濾,應當更早地對數據進行過濾,以減小鏈接時的元素數量。優化

image

有關選擇(selection)的優化spa

  • 儘早執行過濾
  • 重排謂詞,將最具選擇性的謂詞優先應用
  • 分解複雜的謂詞,將之往下推

有關投影(projection)的優化:(列存儲無需進行這兩條優化)設計

  • 儘早進行投影以建立更小的元組並減小中間結果
  • 只投影被須要的屬性

有關鏈接(join)的優化code

  • R⋈S = S⋈R,所以能夠重排多個表的鏈接順序blog

  • 但對於n個表,不一樣的鏈接順序爲卡特蘭數(\(≈4^n\))

image

若是要對全部順序窮舉的話,當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查詢,最樸素的查詢方案多是左圖所示,但經過:

  1. 分解複雜謂詞並向下推
  2. 將笛卡爾積替換爲鏈接
  3. 在鏈接前消除沒必要要的屬性

能夠優化爲右圖所示的方案

image

其餘優化包括

忽略沒必要要的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;

對於嵌套查詢,有兩種方案:

  1. 重寫,將其轉化爲單次查詢
  2. 先進行子查詢,將結果儲存在臨時表中。得出最終結果後將臨時表丟棄。

基於代價的搜索

這種優化方式分爲兩個步驟:

  1. 成本估計
  2. 方案選擇

成本估計

首先要爲特定的執行計劃生成成本估算,而訪問磁盤的消耗始終是查詢中最主要的消耗,而且還要考慮順序訪問仍是隨機訪問,這二者在性能上也有極大差別。

選擇基數

DBMS 在目錄中存儲有關屬性、索引的內部信息。對於每一個關係R,DBMS維護如下信息:

  • \(N_R\) :R中的元組數量
  • \(V(A, R)\):R中在屬性A上不一樣值的數目

則選擇基數(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)\)

也能夠說,選擇性就是指這個範圍的數據出現的機率。

但以上的估計基於三個假設:

  1. 數據是均勻分佈的
  2. 多個謂詞之間相互獨立,能夠獨立計算機率
  3. 內部關係中的key在外表中一樣存在

所以得出的結果是一個估計值,並不精確。

統計直方圖

能夠對第一個假設進行優化,在每一個表中儲存有關數據的直方圖,將數據按範圍進行統計,在計算sel時從直方圖中計算相應的比例。

image

樣本估算

現代DBMS從表中選擇必定的樣本估算sel,當底層表發生顯著變化時更新樣本。

方案選擇

對於簡單的單表查詢(OLTP),經過啓發式規則,利用索引和二分搜索足以獲取良好的性能。

可是對於OLAP中的多表查詢,不一樣的鏈接順序會對性能形成很大影響。而因爲關係的增長會致使可選擇方案指數增加(\(4^n\)),所以須要約束可選擇的空間。

System R 中只考慮左深鏈接樹(Left Deep Join),將選擇空間縮小到 \(n!\),但現代DBMS中再也不總作出這樣的假設。

左深鏈接樹即鏈接的右表必定爲一個基本表,經過流水線鏈接,中間結果不寫入臨時文件。

image

對於鏈接,須要考慮鏈接的順序,不一樣表之間鏈接的方式(Hash join, Sort-Merge join),獲取數據的方式。經過動態規劃對方案進行剪枝。

除了經過動態規劃剪枝以外,當鏈接表過多時,會選擇一些局部最優解的方式:

  1. greedy join enumeration algorithm

    在每次循環中,選擇使總代價最低的方案

    • 多項式時間算法,但結果不必定最優
  2. Randomized algorithm

    隨機重寫查詢方案,利用模擬退火等算法進行優化

  3. Genetic algorithm(遺傳算法)

    經過鏈接方案(結合子代)和隨機突變進行優化

至於爲何是左深鏈接樹,而不是右深鏈接樹?動態規劃的執行優化又是如何實現的?留待後面分析

相關文章
相關標籤/搜索