【連載】如何掌握openGauss資料庫核心技術?秘訣一:拿捏SQL引擎(2)

目錄

openGauss資料庫SQL引擎

openGauss資料庫執行器技術

openGauss儲存技術

openGauss事務機制

openGauss資料庫安全

openGauss資料庫SQL引擎

三、查詢最佳化

SQL語句在編寫的過程中,資料庫應用開發人員通常會考慮以不同的形式來編寫SQL,來達到提升執行效能的目的,那麼為什麼還需要查詢最佳化器來對SQL進行最佳化呢?這是因為一個應用程式可能會涉及到大量的SQL語句,而且有些SQL語句的邏輯極為複雜,資料庫開發人員很難面面俱到的寫出高效能語句,而查詢最佳化器則具有一些獨特的優勢:

§ 查詢最佳化器和資料庫應用程式開發人員之間的資訊不對稱,查詢最佳化器在最佳化的過程中會參考資料庫統計模組自動產生的統計資訊,這些統計資訊從各個角度來描述資料的分佈情況,查詢最佳化器會綜合考慮統計資訊中的各種資料,從而能夠得到一個比較好的執行方案,而資料庫使用者一方面無法全面的瞭解資料的分佈情況,另一方面也很難透過統計資訊構建一個精確的代價模型來對執行計劃進行篩選。

§ 查詢最佳化器和資料庫應用程式開發人員之間的時效性不同,資料庫中的資料瞬息萬變,一個在A時間點執行效能很高的執行計劃,在B時間點由於資料內容發生了變化,它的效能可能就很低,查詢最佳化器則隨時都能根據資料的變化調整執行計劃,而資料庫應用程式開發人員則只能手動的調整SQL語句,和查詢最佳化器相比,它的時效性比較低;

§ 查詢最佳化器和資料庫應用程式開發人員的計算能力不同,目前計算機的計算能力已經大幅提高,在執行數值計算方面和人腦相比具有巨大的優勢,查詢最佳化器對一個SQL語句進行最佳化時,可以從成百上千個執行方案中選擇一個最優方案,而人腦要計算這幾百種方案需要的時間要遠遠長於計算機;

因此,查詢最佳化器是提升查詢效率的非常重要的一個手段,雖然一些資料庫也提供了人工干預執行計劃生成的方法,但是通常而言,查詢最佳化器的最佳化過程對資料庫開發人員是透明的,它自動進行邏輯上的等價變換、自動進行物理執行計劃的篩選,極大的提高了資料庫應用程式開發人員的“生產力”。

依據最佳化方法的不同,最佳化器的最佳化技術可以分為:

§ 基於規則的查詢最佳化(Rule Based Optimization,RBO):根據預定義的啟發式規則對SQL語句進行最佳化。

§ 基於代價的查詢最佳化(Cost Based Optimization,CBO):對SQL語句對應的待選執行路徑進行代價估算,從待選路徑中選擇代價最低的執行路徑作為最終的執行計劃。

§ 基於機器學習的查詢最佳化(AI Based Optimization,ABO):收集執行計劃的特徵資訊,藉助機器學習模型獲得經驗資訊,進而對執行計劃進行調優,獲得最優的執行計劃。

在早期的資料庫中,查詢最佳化器通常採用啟發式規則進行最佳化,這種最佳化方式不不夠靈活,往往難以獲得最優的執行代價,而基於代價的最佳化則能夠針對大多數場景都高效篩選出效能較好的執行計劃,但面對使用者千人千面,日趨複雜的實際查詢場景,普適性的查詢最佳化由於難以捕捉到使用者特定的查詢需求、資料分佈、硬體效能等特徵,難以全方位滿足實際的最佳化需求。

近年來AI技術,特別是在深度學習領域,發展迅速,基於機器學習的最佳化器在建模效率、估算準確率和自適應性等方面都有很大優勢,有望打破RBO和CBO基於靜態模型的限制,透過對歷史經驗的不斷學習,將目標場景的模式進行抽象化,形成動態的模型,自適應地針對使用者的實際場景進行最佳化。openGauss採用基於CBO的最佳化技術,另外在ABO方面也在進行積極探索。

Ⅰ、查詢重寫

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

1.  查詢重寫的概念

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

§ 等價性:原語句和重寫後的語句,輸出結果相同。

§ 高效性:重寫後的語句,比原語句在執行時間和資源使用上更高效。

2.  關係代數等價變換

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

表2  關係代數等價變換

表2中的等價變換規則並不能把所有的情況都列舉出來,例如,如果對σF1(σF2(A)) == σF1∧F2(A)繼續推導,那麼就可以獲得:

σF1(σF2(A)) == σF1∧F2(A) == σF2∧F1(A) == σF2(σF1(A))

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

3.  常見的查詢重寫技術

介紹下openGauss幾個關鍵的查詢重寫技術:常量表達式化簡、子查詢最佳化、選擇下推和等價推理等。

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操作可以有多種實現方法,最佳化器就可以從多種實現方法中選擇最優的一種,就有可能提高查詢的執行效率,另外最佳化器還能夠應用Join Reorder最佳化規則對不同的表的連線順序進行交換,進而有可能產生更好的執行計劃。

示例

4

:該語句為典型的子查詢提升重寫,重寫之後利用Hash 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,3,。。100]共100行資料,那麼查詢語句SELECT t1。c1, t2。c1 FROM t1 JOIN t2 ON t1。c1=t2。c1 WHERE t1。c1=1的則可以透過選擇下推和等價推理進行最佳化,如圖5。

【連載】如何掌握openGauss資料庫核心技術?秘訣一:拿捏SQL引擎(2)

圖4  查詢重寫前後對比圖

圖5  自底向上模式

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

4) 外連線消除

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

示例5

:外連線轉成內連線之後,便於最佳化器應用更多的最佳化規則,提高執行效率。

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的難度,提高查詢的易用性,而檢視本身是虛擬的,因此在查詢重寫的過程中,需要對檢視展開。

示例7

:可以將檢視查詢重寫成子查詢的形式,然後再對子查詢做簡化。

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;

未完待續……

若您對本系列文章感興趣,敬請關注我們的公眾號,我們將在每週二、週四進行更新。

TAG: 查詢T1C1最佳化T2