1. 程式人生 > >關係型資料庫工作原理-查詢優化器之動態規劃,貪婪演算法和啟發式演算法(17)

關係型資料庫工作原理-查詢優化器之動態規劃,貪婪演算法和啟發式演算法(17)

本文翻譯瞭如下章節, 介紹資料庫查詢優化器中尋找最優聯表方案動態規劃,貪婪演算法和啟發式演算法:
這裡寫圖片描述

動態規劃、貪婪演算法和啟發式演算法-Dynamic programming, greedy algorithm and heuristic

關係型資料庫會嘗試之前講過的所有優化途徑。對優化器來說,最重要的工作就是在有限的時間內找出最優的解決方案。

大部分時間,優化器不是找最優解而是找一個還過得去的解決方案。

對於小資料的查詢,窮舉強制找出最優策略是可行的。但是即使最一箇中型資料量表的查詢,也有避免大量無效的計算而使用窮舉方式計算最優解的方法。這就是動態規劃演算法。

動態規劃-Dynamic Programming

隱含在這兩個單詞背後的含義是許多執行策略都是非常類似的。參看下面幾種策略:

它們都具有相同的子樹(A JOIN B).因此不用每次都重複計算這個子樹的成本,我們能夠將計算結果儲存起來,每次計算的時候重複利用它的結果。

更一般的講,我們將處理一個重複計算的問題。為了避免額外的部分結果重複結算,我們使用快取技術。

使用這種快取計算,我們不需要計算(2*N)!/(N+1)!次,我們僅需要計算3的N次方次即可。在前一個樣例中,連線4張表,這意味著計算的次數由336次降到了81次。如果有一個涉及8張表的大查詢語句,計算次數由57657600次降到了6561次。

它的虛擬碼如下:

procedure
findbestplan(S) if (bestplan[S].cost infinite) return bestplan[S] // else bestplan[S] has not been computed earlier, compute it now if (S contains only 1 relation) set bestplan[S].plan and bestplan[S].cost based on the best way of accessing S /* Using selections on S and indices on S */
else for each non-empty subset S1 of S such that S1 != S P1= findbestplan(S1) P2= findbestplan(S - S1) A = best algorithm for joining results of P1 and P2 cost = P1.cost + P2.cost + cost of A if cost < bestplan[S].cost bestplan[S].cost = cost bestplan[S].plan = “execute P1.plan; execute P2.plan; join results of P1 and P2 using A” return bestplan[S]

對一個大的查詢操作,你仍然可以使用動態規劃的方式,但可以使用一些額外的規則(或者啟發式演算法)來移除一些不必要的可選條件:
1. 如果我們僅僅分析一些確定型別的方案(例如只處理left-deep trees).就可以將計算次數由3的N次方降到n*2的n次方。
.
2. 如果我們新增一些邏輯規則來排除某些模式的方案(例如,在給定的連線條件中某張表有索引,那麼除非基於索引連線否則不要使用歸併連線方法)。這樣能建少組合次數又不會將最優解排除掉。
.
3. 如果我們能新增一些規則影響執行流程(順序)(例如:最先執行指定的連線操作),這也能減少很多的組合次數。
.
4. …

貪婪演算法-Greedy algorithms

針對一個非常大的查詢(關聯的表很多)或者想到快速的得到計算結果,另外一種演算法經常被使用,即貪婪演算法。

其基本思想是建立一個用增量的方式構建查詢方案。按這個原則,貪婪演算法能一次性找出方案的最優解。

這個演算法第一步先加入一個join操作,然後用同樣的規則逐步加入其它的join操作。

我們來看一個簡單的例子。我們看一下之前舉的4個join基於5張表的查詢操作(A,B,C,D and E)。為簡化問題,我們僅使用迴圈巢狀連線。我們使用的規則是“使用連線成本的連線”。
1. 我們從5張表中任意一張表開始(就選A表吧)。
2. 我們計算每一個和A表連線的成本(A可以是內連線物件,也可以是外連線物件)。
3. 我們找到A與B表的連線是成本最低的。
4. 然後我們可以計算所有與AB表連線結果的連線成本(AB表連線結果可以作為內連線物件或者外連線物件)。.
5. 我們發現(A JOIN B) JOIN C擁有最低的成本。
6. 然後我們計算所有與(A JOIN B) JOIN C結果的連線成本。依次類推。
7. ….
8. 最終我們找到一個成本最優的連線方案(((A JOIN B) JOIN C) JOIN D) JOIN E)。
9. 因為我們是隨機選擇A表作為開始,我們也可以選B,C,D,E開始,最終得到一個成本最優的方案。

順便說一下,這個演算法有另外一個名稱:最近鄰居演算法。

我不再輸入細節的講,建立一個好的模型,排好序(N*logN)問題就容易解決了。貪婪演算法的時間複雜度是O(N*log(N)) , 相對於動態規劃的 O(3N)的來說,這種方案不要太好。如果有一個涉及20張表的連線查詢,它意味著26次計算與3486784401次計算的差異。

貪婪演算法的問題是我們假設找兩表之間最優成本的連線方案,再通過不斷加入新表的方式,最終得到的就是一個最優的方案。但(譯者注:區域性最優不等於全域性最優):
1. 即使A JOIN B在AB、AC裡面是最優的。
2. (A JOIN C) JOIN B也可能是比(A JOIN B) JOIN C更優的結果。

為了改進這個演算法結果(譯者注:注意是改進,無法徹底解決),我們可以使用不同的規則執行多次貪婪演算法,保留最好的方法。

其它演算法–Other algorithms
【如果你已經厭煩演算法,你可以跳過這個章節,下面講的內容不是很關鍵】。
尋找最優解在計算機研究領域是個熱門話題。他們經常會嘗試對一些更具體的問題或者模型找出更好的方案。例如:
1. 如果查詢語句是一個星型連線(多表連線的一種),一些資料庫會使用特殊演算法。
2. 如果查詢是一個並行查詢語句,一些資料庫也會採用特殊處理演算法。
3. …

人們也在研究其它能代替動態規劃的演算法,在大查詢語句中。貪婪演算法屬於啟發式演算法家族中的一員。

貪婪演算法遵循一條規則,保留上一步演算法找到的結果,並嘗試將它追加到當前這一步找到的方案裡面。

一些演算法遵循這個原則,但在執行過程中並不保證上一步是使用的最優解決。這些演算法被稱為探索演算法(譯者注:如熟知的模擬退火演算法,基因遺傳演算法)。

例如:基因遺傳演算法遵循這樣一個原則,按不保證上一步總是最優的結果:
1. 一種方案代表了一種可行的完整查詢計劃。
2. 代替(貪婪演算法中)儲存一種最優解決方案,基因遺傳演算法在每一步計算的時候儲存P種結果方案。
3. P總查詢計劃是隨機選擇的。
4. 僅擁有最優成本的計劃會被保留。
5. 所有最優的計劃合併在一起產生新的P種計劃。
6. P種新計劃會被隨機修改.
7. 前面3不重複執行T次。
8. 保留最後一次迭代(P種計劃裡面)的最優解。

迭代的次數越多,你得的方案就越好。
是不是很神奇?不,它是自然法則:適者生存。

FYI,基因遺傳演算法在PostgreSQL中實現了,但是我沒有找到該演算法是否會預設使用。

在資料庫中還使用了其它一些啟發式演算法,如模擬退火,迭代改進,兩階段優化等。但是,我不清楚這些演算法是否在企業級資料庫中使用,或者他們僅在學術研究的資料庫中用到。

更多演算法相關的資訊,你可以閱讀如下文章,它裡面介紹了更多可行的演算法: Review of Algorithms for the Join Ordering Problem in Database Query Optimization