openGauss SQL引擎(下)——查詢最佳化

Gauss松鼠會發表於2022-11-08

上一篇 openGauss SQL引擎 (上)中我們介紹了openGauss SQL引擎概覽、SQL解析以及查詢最佳化器的優勢和最佳化技術的分類,本文將詳細介紹 openGauss查詢最佳化的相關內容。

(一)查詢重寫

查詢重寫利用已有語句特徵和關係代數運算來生成更高效的等價語句,在資料庫最佳化器中扮演關鍵角色,尤其在複雜查詢中,能夠在效能上帶來數量級的提升,可謂是“立竿見影”的“黑科技”。下面介紹查詢重寫的基本概念、常見的查詢重寫技術、查詢重寫面臨的挑戰等內容。

1.查詢重寫的概念

SQL是豐富多樣的,應用非常靈活,不同的開發人員依據不同的經驗,編寫的SQL語句也是各式各樣,SQL語句還可以透過工具自動生成。SQL是一種描述性語言,資料庫的使用者只是描述了想要的結果,而不關心資料的具體獲取方式。輸入資料庫的SQL語句很難做到以最優形式表示,往往隱含了冗餘資訊,這些資訊可以被挖掘以生成更加高效的SQL 語句。查詢重寫就是把使用者輸入的 SQL 語句轉換為更高 效的等價SQL。查詢重寫遵循兩個基本原則:

  • 等價性:原語句和重寫後的語句輸出結果相同。
  • 高效性:重寫後的語句比原語句執行時間短,且資源使用更高效。

2.關係代數式等價變換

查詢重寫主要是基於關係代數式的等價變換,關係代數式變換通常滿足交換律、結合律、分配率、串接率等,如表2所示。

表2 關係代數式等價變換
等價變換 內容
交換律 A × B == B × A
A ⨝B == B ⨝ A
A ⨝F B == B ⨝F A – F是連線條件
Π p(σF (B)) == σF (Π p(B)) –- F∈p
結合律 (A × B) × C == A × (B × C)
(A ⨝ B) ⨝ C == A ⨝ (B ⨝ C)
(A ⨝F1 B) ⨝F2 C==A ⨝F1 (B ⨝F2 C) – F1和F2是連線條件
分配律 σF(A × B) == σF(A) × B – F ∈ A
σF(A × B) == σF1(A) × σF2(B) – F = F1 ∪ F2,F1∈A, F2 ∈B
σF(A × B) == σFX (σF1(A) × σF2(B)) – F = F1∪F2∪FX,F1∈A, F2 ∈B
Π p,q(A × B) == Π p(A) × Π q(B) – p∈A,q∈B
σF(A × B) == σF1(A) × σF2(B) – F = F1 ∪ F2,F1∈A, F2 ∈B
σF(A × B) == σFx (σF1(A) × σF2(B)) – 其中F = F1∪F2∪Fx,F1∈A, F2 ∈B
串接律 Π P=p1,p2,…pn(Π Q=q1,q2,…qn(A)) == Π P=p1,p2,…pn(A) – P ⊆ Q
σF1(σF2(A)) == σF1∧F2(A)
表中的等價變換規則並不能把所有的情況都列舉出來,例如,如果對 σF1(σF2(A))==σF1∧F2(A)繼續推導,那麼就可以得到:
σF1(σF2(A))==σF1∧F2(A)==σF2∧F1(A)==σF2(σF1(A))

因此,在熟悉了關係代數的操作之後,就可以靈活地利用關係代數的等價關係進行推導,獲得更多的等價式。這些等價的變換一方面可以用來根據啟發式的規則做優 化,保證等價轉換之後的關係代數表示式的執行效率提高而非降低,例如藉助分配率 可以將一個選擇操作下推,降低上層節點的計算量,另一方面還可以用來生成候選的 執行計劃,再由最佳化器根據估算的代價進行篩選。

3.常見的查詢重寫技術

下面介紹openGauss幾個關鍵的查詢重寫技術:常量表示式化簡、子查詢最佳化、 選擇下推和等價推理、外連線消除、DISTINCT 消除、IN 謂詞展開、檢視展開等。

1)常量表示式化簡

常量表示式即使用者輸入的 SQL 語句中包含運算結果為常量的表示式,如算數表示式、邏輯運算表示式、函式表示式,查詢重寫可以對常量表示式預先計算以提升效率。例如:

示例1: 下面語句為典型的算數表示式查詢重寫,經過重寫之後,避免了在執行時每條資料都需要進行1+1運算。

SELECT * FROM t1 WHERE c1=1+1;SELECT * FROM t1 WHERE c1=2;

示例2: 下面語句為典型的邏輯運算表示式查詢重寫,經過重寫之後,條件永遠為false,可以直接返回0行結果,避免了整個語句的實際執行。

SELECT * FROM t1 WHERE 1=0 AND a=1;SELECT * FROM t1 WHERE false;

示例3: 下面語句包含函式表示式,由於函式的輸入引數為常量,經過重寫之後,直接把函式運算結果在最佳化階段計算出來,避免了在執行過程中逐條資料的函式呼叫開銷。

SELECT * FROM t1 WHERE c1 = ADD(1,1);SELECT * FROM t1 WHERE c1=2;

2)子查詢最佳化

由於子查詢表示的結構更清晰,符合人們的閱讀理解習慣,使用者輸入的SQL語句往往包含了大量的子查詢。子查詢有幾種分類方法,根據子查詢是否可以獨立求解,分為相關子查詢和非相關子查詢。

① 相關子查詢:

相關子查詢是指子查詢中有依賴父查詢的條件,例如:

SELECT * FROM t1 WHERE EXISTS (SELECT t2.c1 FROM t2 WHERE t1.c1=t2.c1)

語句中子查詢依賴父查詢傳入t1.c1的值。

② 非相關子查詢:

非相關子查詢是指子查詢不依賴父查詢,可以獨立求解,例如:

SELECT * FROM t1 WHERE EXISTS (SELECT t2.c1 FROM t2);

語句中子查詢沒有依賴父查詢的條件。

其中,相關子查詢需要父查詢執行出一條結果,然後驅動子查詢運算,這種巢狀迴圈的方式執行效率較低。如果能把子查詢提升為與父查詢同級別,那麼子查詢中的表就能和父查詢中的表直接做Join(連線)操作,由於Join操作可以有多種實現方法,優 化器就可以從多種實現方法中選擇最優的一種,就有可能提高查詢的執行效率,另外 最佳化器還能夠應用JoinReorder最佳化規則對不同表的連線順序進行交換,進而有可能 產生更好的執行計劃。

示例4: 下面語句為典型的子查詢提升重寫,重寫之後利用Semi Join可以提升查詢效能。

SELECT * FROM t1 WHERE t1.c1 IN (SELECT t2.c1 FROM t2); SELECT * FROM t1 Semi Join t2 ON t1.c1 = t2.c1;

3)選擇下推和等價推理

選擇下推能夠極大降低上層運算元的計算量,從而達到最佳化的效果,如果選擇條件存在等值操作,那麼還可以藉助等值操作的特性來實現等價推理,從而獲得新的選擇條件。

例如,假設有兩個表t1、t2,它們分別包含1,2,…,100共100行資料,那麼查詢語句

SSELECT t1.c1, t2.c1 FROM t1 JOIN t2 ON t1.c1=t2.c1 WHERE t1.c1=1

則可以透過選擇下推和等價推理進行最佳化,如下圖所示。

在這裡插入圖片描述

圖 查詢重寫前後對比圖

如圖(1)所示,t1、t2表都需要全表掃描100行資料,然後再做Join操作,生成100行資料的中間結果,最後再做選擇操作,最終結果只有1行資料。如果利用等價推理,可以得到{t1.c1,t2.c1,1}是互相等價的,從而推匯出新的t2.c1=1的選擇條件,並把這個條件下推到t2表上,從而得到圖(4)重寫之後的邏輯計劃。可以看到,重寫之後的邏輯計劃,只需要從基表上獲取1條資料即可,連線時內、外表的資料也只有1條,同時省去了在最終結果上的過濾條件,使效能大幅提升。

4)外連線消除

外連線和內連線的主要區別是對於不能產生連線結果的元組需要補充 NULL值,如果SQL語句中有過濾條件符合空值拒絕的條件(即會將補充的 NULL值再過濾 掉),則可以直接消除外連線。

示例5: 外連線轉成內連線之後,便於最佳化器應用更多的最佳化規則,提高執行效率。SQL語句如下:

SELECT * FROM t1 FULL JOIN t2 ON t1.c1 = t2.c1 WHERE t1.c2 > 5 AND t2.c3 < 10;SELECT * FROM t1 INNER JOIN t2 ON t1.c1 = t2.c2 WHERE t1.c2 > 5 AND t2.c3 < 10;

5)DISTINCT 消除

DISTINCT列上如果有主鍵約束,則此列不可能為空,且無重複值,因此可以不需要 DISTINCT操作,減少計算量。

示例6: c1列上的主鍵屬性決定了無須做 DISTINCT 操作。語句如下:

CREATE TABLE t1(c1 INT PRIMARY KEY, c2 INT);SELECT DISTINCT(c1) FROM t1;SELECT c1 FROM t1;

6)IN 謂詞展開

示例7: 將IN 運算子改寫成等值的過濾條件,便於藉助索引減少計算量。語句如下:

SELECT * FROM t1 WHERE c1 IN (10,20,30);SELECT * FROM t1 WHERE c1=10 or c1=20 OR c1=30;

7)檢視展開

檢視從邏輯上可以簡化書寫SQL的難度,提高查詢的易用性,而檢視本身是虛擬的,因此在查詢重寫的過程中,需要展開檢視。

示例8: 可以將檢視查詢重寫成子查詢的形式,然後再對子查詢做簡化。語句如下:

CREATE VIEW v1 AS (SELECT * FROM t1,t2 WHERE t1.c1=t2.c2);SELECT * FROM v1;SELECT * FROM (SELECT * FROM t1,t2 WHERE t1.c1=t2.c2) as v1;SELECT * FROM t1,t2 WHERE t1.c1=t2.c2;

(二)路徑搜尋

最佳化器最核心的問題是針對某個SQL語句獲得其最優解。這個過程通常需要列舉SQL語句對應的解空間,也就是列舉不同的候選執行路徑。這些候選執行路徑互相等價,但是執行效率不同,需要對它們計算執行代價,最終獲得一個最優的執行路徑。依據候選執行路徑的搜尋方法的不同,將最佳化器的結構劃分為如下幾種模式:

  • 自底向上模式。如下圖所示,自底向上模式會對邏輯執行計劃進行拆分,先建立對錶的掃描運算元,然後由掃描運算元構成連線運算元,最終生成一個物理執行計劃。在這個過程中,由於物理掃描運算元和物理連線運算元有多種可能,因此會生成多個物理執行路徑,最佳化器會根據各個執行路徑的估算代價選擇出代價最低的執行計劃,然後轉交執行器負責執行。

在這裡插入圖片描述

圖自底向上模式
  • 自頂向下模式。該模式總體是運用物件導向思路,將最佳化器核心功能物件化,在詞法分析、語法分析、語義分析後生成邏輯計劃。基於此邏輯計劃,應用物件化的最佳化規則,產生多個待選的邏輯計劃,透過自頂向下的方法遍歷邏輯計劃,結合動態規劃、代價估算和分支限界技術,獲得最優的執行路徑,如下圖所示。

在這裡插入圖片描述

圖 自頂向下模式
  • 隨機搜尋模式。無論是自底向上模式還是自頂向下模式,在參與連線的表的數量比較多的情況下,都會出現列舉時間過長的問題。最佳化器在表比較多的情況下透過隨機列舉的方法對路徑進行搜尋,嘗試在隨機的解空間中獲得次優的執行計劃。

openGauss採用的是自底向上模式和隨機搜尋模式相結合的方式。無論是自頂 向下的搜尋模式還是自底向上的搜尋模式,搜尋的過程也都是一個從邏輯執行計劃向物理執行計劃轉變的過程,例如針對每個表可以有不同的掃描運算元,而邏輯連線運算元也可以轉換為多種不同的物理連線運算元。下面介紹具體的物理運算元。

1.單表掃描路徑搜尋

openGauss採用的是自底向上的路徑搜尋方法,因此路徑生成總是從單表訪問路徑開始。對於單表訪問路徑,一般有兩種:

  • 全表掃描:對錶中的資料逐個訪問。
  • 索引掃描:藉助索引來訪問表中的資料,通常需要結合謂詞一起使用。

最佳化器首先根據表的資料量、過濾條件、可用的索引結合代價模型來估算各種不同掃描路徑的代價。

例如:給定表定義“CREATE TABLE t1(c1 int);”,如果表中資料為 1,2,…, 100 000 000的連續整型數並且在c1列上有 B+樹索引,那麼對於“SELECT*FROM t1 WHEREc1=1;”語句來說,只要讀取1個索引頁面和1個表頁面就可以獲取到資料。然而對於全表掃描,需要讀取1億條資料才能獲取同樣的結果。在這種情況下索引掃描的路徑勝出。

索引掃描並不是在所有情況下都優於全表掃描,它們的優劣取決於過濾條件能夠過濾掉多少資料,通常資料庫管理系統會採用 B+樹來建立索引,如果在選擇率比較高的情況下,B+樹索引會帶來大量的隨機IO,這會降低索引掃描運算元的訪問效率。比如“SELECT * FROMt1 WHEREc1>0;”這條語句,索引掃描需要訪問索引中的全部資料和表中的全部資料,並且帶來巨量的隨機IO,而全表掃描只需要順序地訪問表中的全部資料,因此在這種情況下,全表掃描的代價更低。

2.多表連線路徑搜尋

多表路徑生成的難點主要在於如何列舉所有的表連線順序(Join Reorder)和連線演算法(Join Algorithm)。

假設對t1和t2兩個表做Join操作,根據關係代數中的交換律,可以列舉的連線順序有t1×t2和t2×t1兩種,Join的物理連線運算元有 HashJoin、NestLoop、MergeJoin三種型別。這樣一來,可供選擇的路徑有6種之多。這個數量隨著表的增多呈指數級增長,因此高效的搜尋演算法顯得至關重要。

openGauss通常採用自底向上的路徑搜尋方法,首先生成每個表的掃描路徑,這些掃描路徑在執行計劃的最底層(第一層),然後在第二層開始考慮兩表連線的最優路 徑,即列舉計算出兩表連線的可能性,再在第三層考慮三表連線的最優路徑,即列舉計算出三表連線的可能性,直到最頂層為止,生成全域性最優的執行計劃。

假設有4個表做Join操作,它們的連線路徑生成過程如下:

  • 單表最優路徑:依次生成{1},{2},{3},{4}單表的最優路徑。
  • 兩表最優路徑:依次生成{12},{13},{14},{23},{24},{34}的最優路徑。
  • 三表最優路徑:依次生成{123},{124},{234},{134}的最優路徑。
  • 四表最優路徑:生成{1234}的最優路徑(最終路徑)。

多表路徑問題的核心為JoinOrder,這是 NP(NondeterministicPolynomially,非確定性多項式)類問題。在多個關係連線中找出最優路徑,比較常用的演算法是基於代價的動態規劃演算法,隨著關聯表個數的增多,會發生表搜尋空間膨脹的問題,影響最佳化器路徑選擇的效率,可以採用基於代價的遺傳演算法等隨機搜尋演算法來解決。

另外為了防止搜尋空間過大,openGauss採用了下面三種剪枝策略:

  • 儘可能先考慮有連線條件的路徑,儘量推遲採用笛卡兒積運算。
  • 在搜尋的過程中基於代價估算對執行路徑進行篩選,並基於分支限界技術和啟發式規則進行剪枝,放棄一些代價較高的執行路徑。
  • 保留具有特殊物理屬性的執行路徑,例如有些執行路徑的結果具有有序性,這些執行路徑可能在後續的最佳化過程中避免被再次排序。

3.分散式路徑搜尋

openGauss最佳化引擎可以生成高效的分散式路徑。在分散式架構下,同一個表的資料會分佈到不同的 DN(資料節點)上,建立表的時候可以選擇將資料在每個表上做雜湊(Hash)分佈或者隨機分佈,為了正確執行兩表連線操作,可能需要將兩個表的資料重新分佈才能得到正確的連線結果,因此openGauss的分散式執行計劃中增加了對資料進行重分佈的兩個運算元:

  • Redistribute:將一個表的資料按照執行的雜湊值在所有的 DN上做重分佈。
  • Broadcast:透過廣播的方式重新分佈一個表的資料,保證廣播之後每個 DN上都有這個表的資料的一份副本。

分散式路徑生成時,會考慮兩表及連線條件上的資料是否處於同一個資料節點, 如果不是,那麼會新增相應的資料分發運算元。例如:

CREATE TABLE t1(c1 int, c2 int)  DISTRIBUTE BY hash(c1);CREATE TABLE t2(c1 int, c2 int) DISTRIBUTE BY hash(c2); SELECT * FROM t1 JOIN t2 ON t1.c1=t2.c1;

其中表t1採用的是雜湊分佈方法,其分佈鍵為c1列,表t2採用的也是雜湊分佈方法, 其分佈鍵為c2列,由於SELECT 查詢中選擇條件是在t1.c1和t2.c2上做連線操作, 這兩個列的分佈不同,因此做連線操作之前需要新增資料重分佈來確保連線的資料在同一資料節點上。

其中表t1採用的是雜湊分佈方法,其分佈鍵為c1列,表t2採用的也是雜湊分佈方法, 其分佈鍵為c2列,由於SELECT 查詢中選擇條件是在t1.c1和t2.c2上做連線操作, 這兩個列的分佈不同,因此做連線操作之前需要新增資料重分佈來確保連線的資料在 同一資料節點上。那麼有如下幾種可供選擇的路徑,如下圖所示。

在這裡插入圖片描述

圖 分散式計劃示例

根據分發運算元所需要處理的資料量以及網路通訊所帶來的消耗,可以計算這些路徑的代價,openGauss最佳化引擎會根據代價從中選出最優的路徑。

4.利用物理屬性最佳化

關係的本身可以視為一個集合或者包,這種資料結構對資料的分佈沒有設定,為了提升計算的效能,需要藉助一些資料結構或演算法來對資料的分佈做一些預處理,這 些預處理方法或者利用了物理執行路徑的物理屬性(例如有序性),或者為物理執行路徑建立物理屬性,總之這些屬性經常會在查詢最佳化中發揮巨大的作用。

1)B+樹

如果要查詢一個表中的資料,最簡單的辦法自然是將表中的資料全部遍歷一遍,但是隨著當前資料量變得越來越大,遍歷表中資料的代價也越來越高,而 B+樹就成 了高效查詢資料的有力武器。

1970年,R.Bayer和 E.Mccreight提出了一種適用於外查詢的樹,它是一種平衡的多叉樹,稱為 B樹,B樹就是在表的資料上建立一個“目錄”,類似於書中的目錄,這 樣就能快速地定位到要查詢的資料。

B+樹作為一種資料結構,和查詢最佳化器本身沒有直接的關係,但是資料庫管理系統通常會建立基於 B+樹的索引,而在查詢最佳化的過程中,可以透過索引掃描、點陣圖掃描的方法提高查詢效率,這都會涉及這種 B+樹型別的索引的使用。

2)雜湊表

雜湊表也是一種對資料進行預處理的方法。openGauss資料庫在多個地方使用了雜湊表或借用了雜湊表的思想來提升資料查詢效率:

  • 借用雜湊可以實現分組操作,因為雜湊表天然就有對資料分類的功能。
  • 借用雜湊可以建立雜湊索引,這種索引適用於等值的約束條件。
  • 物理連線路徑中 HashJoin是非常重要的一條路徑。

3)排序

排序也是一種對資料進行預處理的方法。它主要用在以下幾個方面:

  • 借用排序可以實現分組操作,因為經過排序之後,相同的資料都聚集在一起, 因此可以用來實現分組。
  • B樹索引的建立需要藉助排序來實現。
  • 物理連線路徑 MergeJoin路徑需要藉助排序實現。
  • SQL中的 OrderBy操作需要藉助排序實現。

在資料量比較小時,資料可以全部載入到記憶體,這時候使用內排序就能完成排序的工作,而當資料量比較大時,則需要使用外排序才能完成排序的工作,因此在計算排序的代價時需要根據資料量的大小及可使用的記憶體的大小來決定排序的代價。

4)物化

物化就是將掃描操作或者連線操作的中間結果儲存起來,如果中間結果比較大可能需要將結果寫入外存,這會產生IO 代價,因此這種儲存是有代價的。

物化的優點是如果內表的中間結果可以一次讀取並多次使用,那麼就可以將這個中間結果儲存下來多次利用。例如有t1表和t2表做連線,如果t2表作為內表經過掃 描之後,只有5%的資料作為中間結果,其他95%的資料都被過濾掉了,那麼就可以考 慮將這5%的資料物化起來,這樣t1表的每條元組就只和這5%的資料進行連線就可以了。

中間結果是否物化主要取決於代價計算的模型,通常物理最佳化生成物理路徑時對 物化和不物化兩條路徑都會計算代價,最終選擇代價較低的一個。

(三)代價估算

最佳化器會根據生成的邏輯執行計劃列舉出候選的執行路徑,要確保執行的高效, 需要在這些路徑中選擇開銷最小、執行效率最高的路徑。那麼如何評估這些計劃路 徑的執行開銷就變得非常關鍵。代價估算就是來完成這項任務的,基於收集的資料統計資訊,對不同的計劃路徑建立代價估算模型,評估所給出的代價,為路徑搜尋提供輸入。

1.統計資訊

統計資訊是計算計劃路徑代價的基石,統計資訊的準確度對代價估算模型中行數估算和代價估算起著至關重要的作用,直接影響查詢計劃的優劣。openGauss支援使 用 Analyze命令完成對全庫、單表、列、相關性多列進行統計資訊收集。

由於統計資訊直接影響代價計算的準確度,所以統計資訊收集的頻率就是一個非常敏感的引數,如果統計資訊收集的頻率太低,則會導致統計資訊的滯後,相反,如果 過於頻繁地收集統計資訊,則會間接影響查詢的效能。

通常資料庫管理系統會提供手動的收集統計資訊的方法,openGauss支援透過Analyze命令來收集統計資訊,同時資料庫管理系統也會根據資料變化的情況自動決 定是否重新收集統計資訊。例如當一個表中資料的頻繁更新程度超過了一個閾值,那 麼就需要自動更新這個表的統計資訊。在查詢最佳化的過程中,如果最佳化器發現統計信 息的資料已經嚴重滯後,也可以發起統計資訊的收集工作。

表級的統計資訊通常包括元組的數量(N)、表佔有的頁面數(B),而列級的統計資訊則主要包括屬性的寬度(W)、屬性的最大值(Max)、最小值(Min)、高頻值(MCV)等,通常針對每個列會建立一個直方圖(H),將列中的資料按照範圍以直方圖的方式 展示出來,可以更方便地計算選擇率。

直方圖通常包括等高直方圖、等頻直方圖和多維直方圖等,這些直方圖可以從不同的角度來展現資料的分佈情況。openGauss採用的是等高直方圖,直方圖的每個柱 狀體都代表了相同的頻率。

2.選擇率

透過統計資訊,代價估算系統就可以瞭解一個表有多少行資料,用了多少個資料頁面,某個值出現的頻率等,然後根據這些資訊就能計算出一個約束條件(例如 SQL 語句中的 WHERE條件)能夠過濾掉多少資料,這種約束條件過濾出的資料佔總資料 量的比例稱為選擇率。

在這裡插入圖片描述

約束條件可以是由獨立的表示式構成的,也可以是由多個表示式構成的合取正規化或析取正規化,其中獨立的表示式需要根據統計資訊計算選擇率,合取正規化和析取正規化則藉助計算機率的方法獲得選擇率。

合取正規化:P(A and B) = P(A) + P(B) – P(AB)
析取正規化:P(AB) = P(A) × P(B)

假設要對約束條件“A>5andB<3”計算選擇率,那麼首先需要對 A>5和B<3分別計算選擇率,由於已經有了A 列和B 列的統計資訊,因此可以根據統計資訊計算出A 列中值大於5的資料比例,類似的,還可以計算出B 列的選擇率。假設 A>5的選擇率為0.3,B<3的選擇率為0.5,那麼“A>5andB<3”的選擇率為:

P(A >5andB <3)=P(A >5)+P(B <3)-P(A >5)×P(B <3)=0.3+0.5-0.3×0.5=0.65

由於約束條件的多樣性,選擇率的計算通常會遇到一些困難,例如選擇率在計算過程中通常假設多個表示式之間是相互“獨立”的,但實際情況中不同的列之間可能存在函式依賴關係,因此這時候就可能導致選擇率的計算不準確。

3.代價估算方法

openGauss的最佳化器是基於代價的最佳化器,對每條 SQL 語句,openGauss都會生成多個候選計劃,並且給每個計劃計算一個執行代價,然後選擇代價最小的計劃。

當一個約束條件確定了選擇率之後,就可以確定每個計劃路徑所需要處理的行數,並根據行數可以推算出所需要處理的頁面數。當計劃路徑處理頁面的時候,會產生IO 代價,而當計劃路徑處理元組的時候(例如針對元組做表示式計算),會產生 CPU 代價,由於openGauss是分散式資料庫,在 CN 和DN 之間傳輸資料(元組)會產生通訊的代價,因此一個計劃的總體代價可以表示為:

總代價=IO 代價+CPU 代價+通訊代價

openGauss把所有順序掃描一個頁面的代價定義為單位1,所有其他運算元的代價都歸一化到這個單位1上。比如把隨機掃描一個頁面的代價定義為4,即認為隨機掃描一個頁面所需的代價是順序掃描一個頁面所需代價的4倍。又比如,把 CPU 處理 一條元組的代價定義為0.01,即認為 CPU 處理一條元組所需代價為順序掃描一個頁 面所需代價的百分之一。

從另一個角度來看,openGauss將代價又分成了啟動代價和執行代價,其中:

總代價=啟動代價+執行代價

1)啟動代價

從SQL語句開始執行運算元,到該運算元輸出第一條元組為止,所需要的代價稱為啟動代價。有的運算元啟動代價很小,比如基表上的掃描運算元,一旦開始讀取資料頁,就可以輸出元組,因此啟動代價為0。有的運算元的啟動代價相對較大,比如排序運算元,它需要把所有下層運算元的輸出全部讀取,並且把這些元組排序之後,才能輸出第一條元組,因此它的啟動代價比較大。

2)執行代價

從輸出第一條元組開始至查詢結束,所需要的代價稱為執行代價。這個代價中又可以包含 CPU 代價、IO 代價和通訊代價,執行代價的大小與運算元需要處理的資料量有關,與每個運算元完成的功能有關。處理的資料量越大、運算元需要完成的任務越重,則執行代價越大。

3)總代價

代價計算是一個自底向上的過程,首先計算掃描運算元的代價,然後根據掃描運算元的代價計算連線運算元的代價以及 Non-SPJ運算元的代價。下圖是代價計算示例。

在這裡插入圖片描述

圖 代價計算示例

如圖所示,SQL查詢中包含兩張表,分別為t1、t2,它的某個候選計劃的計算過程如下:

  • 掃描t1的啟動代價為0.00,總代價為13.13。總代價中既包括了掃描表頁面的IO 代價,也包 括 了 對 元 組 進 行 處 理 的 CPU 代 價,同 理 可 以 獲 得 對t2 表 掃 描 的代價。
  • 由於連線條件(t1.c1=t2.c2)中的列與兩表的分佈列不同,因此該計劃對t2進行了廣播(Broadcast),廣播運算元的總代價為15.18,此代價已經包括了順序掃描t2的代價13.13。
  • 使用 HashJoin時,必須先為內表的資料建立 Hash表,因此 HashJoin具有啟動代價,它的啟動代價是13.29,HashJoin的總代價為28.64。
  • 聚集運算元的啟動代價為28.69,總代價為28.79。
  • 以此類推,此計劃最終的啟動代價為29.31,總代價為29.72。

小結

本文主要從SQL解析器、查詢重寫、代價估算、路徑搜尋等方面講解了 SQL引擎各個模組的基本功能和原理,在此基礎上讀者可以結合具體的 SQL 最佳化案例分析進一步加深對最佳化器最佳化技術的理解。


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

相關文章