深度解讀 Cascades 查詢最佳化器

KaiwuDB發表於2023-12-12

資料庫中查詢最佳化器是資料庫的核心元件,其決定著 SQL 查詢的效能。Cascades 最佳化器是 Goetz 在 volcano optimizer generator 的基礎上最佳化之後誕生的一個搜尋框架。

本期技術貼將帶大家瞭解 Cascades 查詢最佳化器。首先介紹 SQL 查詢最佳化器,接著分析查詢最佳化基本原理,最後對 Cascades 查詢最佳化器進行重點介紹。

一、SQL 查詢最佳化器

使用者與資料庫互動時只需要輸入宣告式 SQL 語句,資料庫最佳化器則負責將使用者輸入的 SQL 語句進行各種規則最佳化,生成最優的執行計劃,並交由執行器執行。最佳化器對於 SQL 查詢具有十分重要的意義。

如圖 1 所示,SQL 語句經過語法和詞法解析生成抽象語法樹(AST),經過 基於規則的查詢最佳化(Rule-Based Optimizer)基於代價的查詢最佳化(Cost-Based Optimizer)生成可執行計劃。

圖 1

  • 基於規則的最佳化演算法: 基於規則的最佳化方法的要點在於結構匹配和替換。應用規則的演算法一般需要先在關係代數結構上匹配一部分區域性的結構,再根據結構的特點進行變換乃至替換操作。

  • 基於成本的最佳化演算法: 現階段主流的方法都是基於成本(Cost)估算的方法。給定某一關係代數代表的執行方案,對這一方案的執行成本進行估算,最終選擇估算成本最低的方案。儘管被稱為基於成本的方法,這類演算法仍然往往要結合規則進行方案的探索。基於成本的方法其實是透過不斷的應用規則進行變換得到新的執行方案,然後對比方案的成本優劣進行最終選擇。

二、查詢最佳化的基本原理

最佳化器一般由三個元件組成: 統計資訊收集開銷模型計劃列舉

如圖 2 所示,開銷模型使用收集到的統計資訊以及構造的不同開銷公式,估計某個特定查詢計劃的成本,幫助最佳化器從眾多備選方案中找到開銷最低的計劃。

圖 2

SQL 語句查詢最佳化基於關係代數這一模型:

  • SQL 查詢可以轉化為關係代數;

  • 關係代數可以進行區域性的等價變換,變換前後返回的結果不變但是執行成本不同;

  • 透過尋找執行成本最低的關係代數表示,我們就可以將一個 SQL 查詢最佳化成更為高效的方案。

尋找執行成本最低的關係代數表示,可以分為 基於動態規劃的自底向上基於 Cascades/Volcano 的自頂向下兩個流派。

  • 自底向上搜尋:從葉子節點開始計算最低成本,並利用已經計算好的子樹成本計算出母樹的成本,就可以得到最優方案;

  • 自頂向下搜尋:先從關係運算元樹的頂層開始,以深度優先的方式來向下遍歷,遍歷過程中進行剪枝。

自底向上的最佳化器從零開始構建最優計劃,這類方法通常採用動態規劃策略進行最佳化,採用這類方法的最佳化器包括 IBMSystem R。自頂向下的最佳化策略的最佳化器包括基於 Volcano 和 Cascades 框架的最佳化器。

三、Cascades 查詢最佳化器

Cascades 查詢最佳化器採用自頂向下的搜尋策略,並在搜尋過程中利用 Memo 結構儲存搜尋的狀態。

Cascades 關鍵元件構成:

  • Expression:Expression 表示一個邏輯運算元或物理運算元。如 Scan、Join 運算元;

  • Group:表示等價 Expression 的集合,即同一個 Group 中的 Expression 在邏輯上等價。Expression 的每個子節點都是以一個 Group 表示的。一個邏輯運算元可能對應多個物理運算元,例如一個邏輯運算元 Join(a,b),它對應的物理運算元包括{HJ(a, b), HJ(b, a), MJ(a, b), MJ(b, a), NLJ(a, b), NLJ(b, a)}。我們將這些邏輯上等價的物理運算元稱為一個 Group(組)。注:HJ 表示 HashJoin 運算元,MJ 表示 MergeJoin 運算元,NLJ 表示 NestLoopJoin 運算元;

  • Memo:由於 Cascades 框架採用自頂向下的方式進行列舉,因此,列舉過程中可能產生大量的重複計劃。為了防止出現重複列舉,Cascades 框架採用 Memo 資料結構。Memo 採用一個類似樹狀(實際是一個圖狀)的資料結構,它的每個節點對應一個組,每個組的成員透過連結串列組織起來;

  • Transformation Rule:是作用於 Expression 和 Group 上的等價變化規則,用來擴大最佳化器搜尋空間。

Cascades 首先將整個 Operator Tree 按節點複製到一個 Memo 的資料結構中,Memo 由一系列的 Group 構成,每個運算元放在一個 Group,對於有子節點的運算元來說,將原本對運算元的直接引用,變成對 Group 的引用。

圖 3

如圖 3 所示,生成該語法樹的 Memo 初始結構。Memo 結構中一個圓角框代表一個運算元,圓角框右下角是對其 Children’s Groups 的引用,左下角是識別符號。生成初始的 Memo 結構後,可以採用 transform rule 進行邏輯等價轉換,規則如下:

  • 對於一個邏輯運算元,其所有基於關係代數的等價表示式儲存在同一個 Group 內,例如 join(A,B) -> join(B,A);

  • 在一個 Group 內,對於一個邏輯運算元,會生成一個或多個物理運算元,例如 join -> hash join,merge join,NestLoop join;

  • 一個 Group 內,一個運算元,其輸入(也可以理解為subplan)可以來自多個 Group 的表示式。

在圖 4 中,描述了一個部分擴充套件的 Memo 結構,與圖 1 中的初始 Memo 相比,在同一個 Group 內,增加了等價的邏輯運算元,以及對應的物理運算元。

圖 4

在探索的過程中,最佳化器就會透過開銷模型 Coster 藉助統計資訊來計運算元步驟的開銷,遍歷完每個 Memo Group之後,歸總得到每個完整計劃的總開銷,最終選擇 Memo 中開銷最低的計劃。

圖 5

圖 5 中有三個 Group,分別對應三個邏輯運算元:Join(a, b), GET(a) 和 GET(b)。Group 1(Group 2)中包含了所有對應 GET(a) (GET(b))的物理運算元,我們可以估算每個物理運算元的代價,選取其中最優的運算元保留下來。

為了防止列舉過程出現重複列舉某個表示式,Memo 結構體中還包含一個雜湊表(exprHT),它以表示式為雜湊表的鍵,用來快速查詢某個表示式是否已經存在於 Memo 結構體中。

Cascades 採用自頂向下的方式來進行最佳化,以計劃樹的根節點為輸入,遞迴地最佳化每個節點或表示式組。如圖所示,整個最佳化過程從 Group 0 開始,實際上要先遞迴地完成兩個子節點(Group 1 和 Group 2)的最佳化。

因此,實際的最佳化完成次序是 Group 1 -> Group2 -> Group 0。在最佳化每個 Group 時,依次最佳化每個組員;在最佳化每個組員時,依次遞迴地最佳化每個子節點。依次估算當前組裡每個表示式 e 的代價 cost(e),選擇最低得代價結果儲存在 bestHT 中。最佳化結束時,查詢 Join(a,b)對應的 Memo 結構體,獲取最低的執行計劃。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70027415/viewspace-2999694/,如需轉載,請註明出處,否則將追究法律責任。

相關文章