天天看點

UniqueKey:讓你的查詢跑的更快

執行計劃的好壞對于查詢的性能起着至關重要的影響,那麼一個執行計劃的生成都和哪些因素相關? 内容簡要 1. 什麼是Uniquekey 2. 進一步識别UniqueKey 3. UniqueKey的應用和實踐 UniqueKey是在一個結果集中能夠唯一确定一條記錄的一組表達式,對于一個複雜的查詢,這個結果集通常是執行到一定階段的中間結果集。 比如說有一個5張表連接配接,第一個階段是每一個結果的單個結果集,在這個階段每一張表都有自己的UniqueKey,當第一張表和第二張表建立之後,建立的結果又有一個怎樣的UniqueKey?前三張表建立之後它的UniqueKey是怎麼樣的,整個過程中都可以判斷它的UniqueKey是什麼樣,基于這些資訊,優化器可以做出更加明智的決斷! ![image.png](https://ucc.alicdn.com/pic/developer-ecology/e5c4ac27a28f446cb20fea457269dfdc.png)

UniqueKey!=Unique Index

UniqueKey和Unique Index有很強的關聯性,通常UniqueKey的起點就是Unique Index,但它們是不同的,首先從簡單的例子看起:

UniqueKey:讓你的查詢跑的更快

這種場景是UniqueKey的最簡單的場景,那麼a是唯一的UniqueKey嗎?

答案是否定的,因為唯一索引允許有多個空值存在。A并不是唯一的,因為Unique Index允許多個NULL。排除NULL的因素即可。

如何去保證一個Unique Index出來的結果是不包含空的:

第一種場景是在定義表結構的時候,列上面就有非空限制,就可以很清楚的判斷它裡邊一定不會有空值;第二種判斷會根據使用者輸入的查詢,就是在表裡它可能會存在多個空值,但是對于這個結果集裡邊a它一定不會有空值存在;第三種Select a from t where a in一個子查詢。如果a是一個空a in子查詢,一定也不會傳回True。是以a它也一定不是是空的;第四種就會不那麼直覺,它是Select a from t where func(a)大于10,并且函數它的Isstrict的屬性是True,Isstrict的屬性True它表達的意思是如果它的輸入參數a是一個空,那麼它的傳回值一定也是空,那麼它的傳回值就一定不會大于10。

也可以簡單的說,唯一索引再加上一個非空限制,最終就可以轉變為一個唯一鍵。例如下圖所示:

UniqueKey:讓你的查詢跑的更快

再看一個組合索引的情況,如下圖所示:

UniqueKey:讓你的查詢跑的更快

至此為止讨論的都是唯一鍵和單表查詢,實際上更複雜的場景來自于表連接配接。來看一個兩張表連接配接的例子,如下圖:

UniqueKey:讓你的查詢跑的更快

對于這樣的一個結果集,它裡邊是沒有唯一鍵的,那麼對于一個表查詢來說,它的唯一鍵在哪些場景下面是可以繼承下來的呢?其他場景會産生UniqueKey的例子,如下圖:

UniqueKey:讓你的查詢跑的更快

在現實場景中的UniqueKey會隐藏在一個複雜查詢中的一部分。需要在一個執行計劃的産生過程中,在任何一個階段準确判斷出UniqueKey的屬性,以便優化器産生更優的執行計劃。

UniqueKey:讓你的查詢跑的更快

UniqueKey的用途

唯一鍵的用途?

第一減少半連接配接;

第二減少無用連接配接;

第三可以去除一些不需要的Distinct操作。

1.減少半連接配接  如下圖所示:

UniqueKey:讓你的查詢跑的更快
UniqueKey:讓你的查詢跑的更快

這裡的t2有100萬條資料,要把這100萬條資料全部掃描一遍,根據執行情況,可以看到它執行的時間是372毫秒。

UniqueKey:讓你的查詢跑的更快

整個過程中隻對于t整個循環了10次,而不是原來的100萬次,它的執行時間也從原來的370多毫秒下降到了0.235毫秒。

但為什麼前面的就不能這樣做,後面的這個就可以這樣做呢?

原因在于 a它是一個唯一鍵,而 b不是唯一鍵,在 a是一個唯一鍵的場景下面,可以做一些查詢的改寫。

半連接配接的轉化過程如下:

本來是Select from t2 wehre b in(Select a from t )它可以将這個半連接配接轉換成一個正常的Inner Join;Select t2. from t2,t where t2.b=t.a,這裡的t2.b=t.a,a又是一個UniqueKey通過這樣的教育,并不會讓原來的t2的一條結果變成多條結果,是以這個語義還是相等的。 當把它們轉變成一個正常的連接配接以後,優化器就會同時去看,拿着t2去教育t生成一些執行計劃,也會拿着t去教育t2去生成另外的一批執行計劃。

最後優化器會認為拿着t去教育t2可以得到一個更好的執行計劃,也就是前面看到的,掃描外表隻有10行,整個過程中隻需要循環10次即可達到目的。

這裡查詢的改寫依賴于 a它是一個唯一鍵。

UniqueKey:讓你的查詢跑的更快

2.減少無用連接配接:

最終出現的這樣的一個查詢的邏輯分析的一個結果,就是在這種場景下面,t2裡邊的任何的一個結果集都不會被重複,并且每一條資料都應該被傳回,并且都不會被重複,這個結構可以直接去掃描一下,t2跟t1沒有任何的關系。

最終可以看到對于 Left Join這樣的操作,産生的一個執行計劃,僅僅是對t2進行了一個全表掃描和 t沒有任何的關系。

那麼使用者為什麼會寫一個這樣的查詢?會寫出這種沒有用的連接配接語句呢?

實際上這是一個使用場景,假設有一個表,它有很多的屬性,這些屬性全部放到一起,表就會非常的大,而這個時候可以把一個寬表拆分成很多的窄表,然後拆分成窄表以後,如果是隻去通路其中的個别列的話,其實是訪通路了一個更小的結果集,它的性能就會更好。

但有一樣是不變的,就是說每個人如果想去獲得更多的列,他就要去寫很多不同的Join,這個時候其實有一種做法,建立一個視圖,把所有的表全部通過Left鍵把它給連接配接起來,這個時候使用者看到的是一個像寬表一樣的,它隻有去查詢這個視圖,去檢索它需要的量。如果說它隻檢索很少的量,優化器就會直接通過這項技術把那些無關的表的Left那些Join全部給去掉,性能也會變得很好,使用者也會非常的友善。在PG内部的優化視圖裡邊,内置的一些視圖裡邊很多都采用了這樣的技術。

UniqueKey:讓你的查詢跑的更快

Join會使得一方的結果集過濾掉或者一行變多行。

.Left Join保證了左表的資料一定不會被過濾掉。

.Join右表的條件又是UniqueKey,是以左表的資料一定不會被重複。

.最終隻需要掃描一遍t2即可,完全忽略掉t1.

實用場景:将一個寬表拆分面多個窄表,然後通過left join來建立視圖,應用按需檢索列。

消除無用的Distinst操作

正常的 Distinst的操作,Select Distinst a c d首先要得到一個結果集,然後對結果集進行一次去除,如果它的資料量是很大的,這個去除它本身也可能要花費很長的時間,但是如果說可以判斷出這個結果集已經是唯一的了,去重操作是可以不做的。看右邊的查詢,它是Select Disstind b c d那麼a本來就是唯一的,加上c、d,它自然還是唯一的Distinst的操作。可以完全忽略這樣的操作,省掉了一個代價很高的節點。

這樣一個功能的意義在現實生活中,在平時開發應用程式的時候,有可能也會進行很多的表連接配接,當建立完表連接配接以後,其實也很難判斷它到底是不是唯一的,這個時候就會去加一個Distinct的操作,同時有一些架構也比較适合去加這種Distinct的操作。

如果資料庫的核心識别不了這些唯一鍵,它就必須要去生成一個去除的節點,可能要消耗很大的資源,但是如果說能夠識别這樣的一個資訊,這個節點就可以直接去除,進而達到很好的性能。如下圖所示:

UniqueKey:讓你的查詢跑的更快

阿裡雲在今年1月份的時候送出了一個Patch, Patch可以更加系統化的去識别,在一個複雜階段的UniqueKey,它的唯一鍵是什麼?

相對提供了一個非常完整的監測機制,同時又增強了一個Distinct的操作。将UniqueKey用于Distinct的這種場景下邊。這個功能有望會在PGConf和入到PGConf14這個版本裡邊。

UniqueKey:讓你的查詢跑的更快

PolarDB(相容PG/Oracle文法)

特點:

-計算存儲分離

-計算節點點瞬間擴容

-存儲節點按量收費

-高度相容Oracle文法

UniqueKey:讓你的查詢跑的更快

它是一個一寫多讀的架構,但和傳統的主備不同,它采用的是計算存儲分離的架構,并且底層是一個共享的檔案系統Polar Store。

這種架構的好處是計算存儲分離,計算節點可以做到瞬間的擴容,并且存儲節點也可以按量付費。

在文法層面,我們高度相容Oracle文法,使用者可以輕易的從Oracle資料庫遷移到Polar Store,在讀節點和寫節點之上,還有一個MaxSQL節點,使用者隻需要連接配接MaxSQL節點,就會根據當時的準備延遲情況,将請求轉發給讀寫節點或者隻讀節點,進而充分的利用硬體資源。