對於n個表的鏈接,數量爲卡特蘭數,近似\(4^n\),所以爲了減小枚舉空間,早期的優化器僅考慮左深鏈接樹,將數量減小爲\(n!\)html
但爲何是左深鏈接樹,而不是其餘樣式呢?node
若是join算法爲index join或者hash join,當兩張表進行鏈接的時候,須要爲左表創建哈希映射或者搜索索引,鏈接時直接尋找對應的元素:算法
join ⋈2 必須等到⋈1 的所有元組輸出以後才能生成它的映射表/索引。即只有⋈1 結束後,⋈2才能開始輸出元組。而此時⋈3必須等待,直到⋈2完成。oop
對於多個表的鏈接,當⋈i正在執行時,⋈i+1處於半活躍的狀態,它累積⋈i的輸出到緩衝區並創建映射,然後面的⋈i+2到⋈n均處於空閒狀態。優化
當執行鏈接⋈1時,須要爲⋈1中的表分配內存,而後將輸出的元組一樣儲存在內存中。而如前所述,只有⋈1結束時⋈2才能開始,所以⋈1結束時能夠直接釋放掉以前佔用的內存空間。spa
而對於其餘形式的樹,例如右深鏈接樹,由於左側的操做數都是一個關係,全部的join鏈接符均可覺得左表創建映射表/索引,會佔用大量的內存空間。.net
所以對於Hash Join,採用左深鏈接樹能夠減小執行計劃對內存的需求。code
當join算法爲nested-loop join時,若是採用右深鏈接樹,結果會更糟糕:htm
如圖,執行⋈3時會致使屢次訪問⋈3的第二個操做數,使得該子查詢屢次執行,會屢次訪問表T、R、S增長讀取磁盤的次數。blog
最佳的鏈接順序便是中間結果中產生最少元組數量的鏈接順序
由於不一樣的鏈接順序都會訪問每一個表一次,而錶鏈接的中間結果每每須要寫入磁盤中暫時儲存,所以中間結果元組數量越少,讀取磁盤次數越少。
所以咱們定義 cost for join 便是指鏈接後產生的中間結果的個數。
而不去鏈接怎麼知道中間結果的個數呢?那就須要用到上一篇博客中提到的謂詞的選擇性和數據直方圖,估算鏈接後產生的元組個數。
對於三個關係的鏈接,須要維護以下的數據圖:
首先是相互鏈接關係的列表,而後是鏈接後的元組總數和鏈接的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四個關係相鏈接,咱們已經得出兩個關係的最優解以下圖所示:
那麼假設如今有
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,那麼
由於 T(S ⋈ T) = 2000, 所以 {S, T} ⋈ R 即爲 s=R, S, T 的最優順序。
將length(s)=3的四種狀況依次計算,再求得四個關係相鏈接的最優順序。
動態規劃的主要意義仍是尋找次優的鏈接順序,而且其搜索空間依然很大,須要\(O(n*2^{n-1})\),當表的數量爲兩位數時依然須要較長時間來響應。