Database | 淺談Query Optimization (2)

珠璣位發表於2021-04-10

為什麼選擇左深連線樹

對於n個表的連線,數量為卡特蘭數,近似\(4^n\),因此為了減少列舉空間,早期的優化器僅考慮左深連線樹,將數量減少為\(n!\)

但為什麼是左深連線樹,而不是其他樣式呢?

如果join演算法為index join或者hash join,當兩張表進行連線的時候,需要為左表建立雜湊對映或者搜尋索引,連線時直接尋找對應的元素:

Database | 淺談Query Optimization (2)

join ⋈2 必須等到⋈1 的全部元組輸出之後才能生成它的對映表/索引。即只有⋈1 結束後,⋈2才能開始輸出元組。而此時⋈3必須等待,直到⋈2完成。

對於多個表的連線,當⋈i正在執行時,⋈i+1處於半活躍的狀態,它累積⋈i的輸出到緩衝區並建立對映,而後面的⋈i+2⋈n均處於空閒狀態。

當執行連線⋈1時,需要為⋈1中的表分配記憶體,然後將輸出的元組同樣儲存在記憶體中。而如前所述,只有⋈1結束時⋈2才能開始,因此⋈1結束時可以直接釋放掉之前佔用的記憶體空間。

Database | 淺談Query Optimization (2)

而對於其他形式的樹,例如右深連線樹,因為左側的運算元都是一個關係,所有的join連線符都可以為左表建立對映表/索引,會佔用大量的記憶體空間。

因此對於Hash Join,採用左深連線樹可以減少執行計劃對記憶體的需求。

當join演算法為nested-loop join時,如果採用右深連線樹,結果會更糟糕:

image

如圖,執行⋈3時會導致多次訪問⋈3的第二個運算元,使得該子查詢多次執行,會多次訪問表T、R、S增加讀取磁碟的次數。

尋找最佳連線順序

最佳的連線順序即是中間結果中產生最少元組數量的連線順序

因為不同的連線順序都會訪問每個表一次,而表連線的中間結果往往需要寫入磁碟中暫時儲存,因此中間結果元組數量越少,讀取磁碟次數越少。

因此我們定義 cost for join 即是指連線後產生的中間結果的個數。

而不去連線怎麼知道中間結果的個數呢?那就需要用到上一篇部落格中提到的謂詞的選擇性資料直方圖,估算連線後產生的元組個數。

對於三個關係的連線,需要維護如下的資料圖:

Database | 淺談Query Optimization (2)

首先是相互連線關係的列表,然後是連線後的元組總數和連線的cost,以及這幾個關係的最佳連線順序。

然後對給定的n個表,將其分解成n個n-1的表的連線,再逐層分解,先求得兩個關係的最佳連線方式。最優解即是這些子問題的組合。

演算法的虛擬碼如下:

 j = set of join nodes
 for (i in 1...|j|):    //一開始尋找單個join的最佳方案,再向上延伸
     for s in {all length i subsets of j}   //尋找s的最優連線
       bestPlan = {}
       //i-1的最優解都已經儲存在optjoin中
       //只需要考慮再加一個表的情況
       for ss in {all length i-1 subsets of s}   
            subplan = optjoin(ss) 
            //optjoin 可以理解為一個雜湊表,儲存對應ss的最優連線
            plan = best way to join (s-ss) to subplan
            if (cost(plan) < cost(bestPlan))
               bestPlan = plan
      optjoin(s) = bestPlan
 return optjoin(j)

具體而言,假設現在是R、S、T、U四個關係相連線,我們已經得出兩個關係的最優解如下圖所示:
Database | 淺談Query Optimization (2)

那麼假設現在有

i=3, s=R,S,T
//那麼對於ss
ss=R,S or R,T or S,T

計算出三種s的cost,找出bestplan,則

optjoin(R,S,T) = bestplan

我們先不考慮謂詞選擇性,直接將生成的元組個數作為cost,那麼

Database | 淺談Query Optimization (2)

因為 T(S ⋈ T) = 2000, 因此 {S, T} ⋈ R 即為 s=R, S, T 的最優順序。

將length(s)=3的四種情況依次計算,再求得四個關係相連線的最優順序。

動態規劃演算法的缺點

  1. 缺乏擴充套件性:當需要加入新的join方法時,需要修改大量程式碼。如果增加新的operator,比如aggregation,那麼修改就更加困難。
  2. 對Join順序優化的問題非常適合,但是卻不容易適用其他的優化方法,比如對GroupBy或者Union的優化。

動態規劃的主要意義還是尋找次優的連線順序,並且其搜尋空間依然很大,需要\(O(n*2^{n-1})\),當表的數量為兩位數時依然需要較長時間來響應。

參考

Trafodion優化器簡述

left deep tree

dyn-prog-join

相關文章