查詢優化應該是數據庫領域最難的topic算法
當前查詢優化,主要有兩種思路,sql
Rules-based,基於先驗知識,用if-else把優化邏輯寫死數據庫
Cost-based,試圖去評估各個查詢計劃的cost,選取cost比較小的oop
一個sql query的處理流程,優化
先是Parser,生成抽象語法樹ast,Binder會去作元數據對應,把parse出來的name對應到數據庫中的結構,表,字段等ui
而後Rewriter就是Rules-based的改寫,而Optimizer是cost-based的優化spa
作查詢優化的前提是,查詢的結果是不能變的3d
不管你查詢怎麼優化,最終獲得的結果是同樣的,那麼就稱他們是,關係代數等價blog
對於不一樣的operator,有些通用的優化rules,索引
這裏給些例子,
Selections
對於selection,儘可能下推,謂詞下推,儘可能早作
Projections
projection也是應該儘早去作,不須要的字段就根本不用讀出來
Joins
對於Join是符合交換律和結合律的,可是對於多表join,你須要嘗試的可能性是很是多的
cost-based的查詢優化,關鍵就是要可以知道cost
如何預估cost是很複雜的問題
當前的思路,就是咱們會事先對數據表,列,索引作些統計,並存儲到catalog裏面,而後後面就根據這些統計數據來預估cost
主要的統計數據,包含兩項,行數和每一列的distinct values的個數
而後有個概念,selection cardinality,兩個相除,就是平均每一個value多少行
這裏的假設是數據是均勻分佈,很naive
有了這些概念,咱們就能夠來定義複雜謂詞,操做,的selectivity,篩選率
Equality,Range
Equality的定義有些confuse,SC(P),SC(age=2)啥意思?
其實以range的邏輯看,這裏就應該是,A列一共有5個值,當前Equality只取其中一個,因此五分之一,就這麼簡單
Conjunction和Disjunction
Join,對於join,這個cost算的很粗糙,好比R表中的行數 * 每行在S中的cardinality
平均分佈問題
當前算cost,都是假設平均分佈,這個明顯是很不合理的
可是若是對於每一個value都去記錄一個統計,明顯不可行,太多了
因此有以下幾種近似方法,
一種,每一個value bucket都去統計太多,那就分組,這樣每一個組記錄一個統計,組內仍然假設平均分佈
分組能夠有兩種方式,第一個是固定bucket數,或者固定組內bucket統計和差很少,叫等寬
還有種更直接的方式,sampling
上面說的cost estimation的方法都很naive,可是若是咱們能準確的預估執行計劃的cost,那麼如何真正的作查詢優化?
這裏分爲三種狀況,
Single Relation,Multiple relations, Nested Sub-queries
Single Relation,比較簡單,單關係表的查詢
因此關鍵就是選擇合理的access method,是順序掃描,仍是用各類索引
這裏有個概念,sargable,數據庫專有概念,意思是查詢或執行計劃能夠用索引來優化的
OLTP的查詢每每都是sargable的,因此經過簡單的啓發式的方式就能夠找到優化方法
Multiple relations
多關係表join就比較複雜了
除了要選擇各個表的access method
還須要選擇各個表的join順序和join算法
其中選擇join順序是個cost很高的事情,由於可能性和search space會比較的大
這裏介紹IBM R的方法,它把join順序簡化成,只考慮Left-deep tree(右圖最左邊這個)
這樣search space會大幅縮小,另外特地選擇left-deep tree的緣由是,這種join結構,比較容易pipeline執行,好比下圖,a b的join結果直接能夠用於和c join,當中間結果不是很大的時候,不須要不斷地的把結果寫到磁盤
對於Multiple relations,能夠嘗試用動態規劃來選擇best plan
更爲形象的例子來講明如何逐步篩選plan
第一步是選擇join的順序,能夠首先把明顯低效的,好比作cross-products的Prune掉
而後根據cost model找出best的plan
第二步是選擇join算法,這裏有Nested Loop和Hash Join
第三步是選擇access method,
Nested Sub-queries
第三種更爲複雜一些,在有子SQL的時候,如何優化?
有兩種方式,一種是經過rewrite,把嵌套的子語句flatten掉,如右圖的例子
另外一種方法,是decompose,以下面的例子
子句是要獲取最大的rating,這個反覆去執行必定是低效的,因此,乾脆把這個語句拿出來單獨執行,結果放在臨時表,而後執行主語句的時候把值填回去