1. 程式人生 > >資料庫查詢優化技術(二):子查詢優化

資料庫查詢優化技術(二):子查詢優化

查詢的基本操作

 

 

1選擇操作

對應的是限制條件(格式類似“field<op>consant”, field表示列物件,op是操作符如"="、">"等)。

操作物件是二維表中的行

優化方式:

選擇操作下推

目的:

是儘量減少連線操作前的元素組,使得中間臨時關係儘量少(元組數少,連線得到的元組數就少)

好處:

這樣可能減少IO和CPU的消耗、節約記憶體空間。

 

2投影操作。

對應的SELECT查詢的目的列物件。

優化方式:投影操作下推

目的:

是儘量減少連線操作前的列數,使得中間臨時關係儘量少(特別注意差別:選擇操作是使元祖的個數”儘量少“,投影操作是使一條元祖”儘量小“)

好處:

這樣雖然不能減少IO(多數資料庫儲存方式是行儲存,元祖是讀取的最基本單位,所以要想操作列則必須讀取一行資料),但可以減少連線後的中間關係的元祖大小,節約記憶體空間。

 

3連線操作

對應的是連線物件條件(格式類似“field_1<op>field_2”,field_1和field_2表示不同表的列物件,op是操作符如“=”、“>”等),表示兩個表連線的條件。

 

Q:連線操作有優化方式麼?

 

連線操作涉及到的兩個子問題

3.1多表連線中每個表被連線的順序決定著效率

如果一個查詢語句只有一個表,則這樣的語句很簡單;但如果有多個表,則會設計表之間以什麼樣的順序連線最高效(如A、B、C三表連線,如果ABC、ACB、BCA等連線之後的結果集一樣,則哪種連線次序的效率最高,是需要考慮的問題)。

3.2多表連線每個表被連線的順序被使用者語義決定

查詢語句多表連線有著不同的語義(如是笛卡爾集、內連線、還是外連線中的左外連線等),這決定著表之間的前後連線次序是不能隨意更換的,否則,結果集中資料是不同的。因此,表的前後連線次序是不能隨意交換的。

查詢的2種類型

 

根據SQL語句的形式特點,還可以做如下區分:

1針對SPJ的查詢優化。

基於選擇、投影、連線三種基本操作相結合的查詢所做的優化。

2針對非SPJ的查詢優化

在SPJ基礎上存在GROUPBY操作的查詢,這是一種較為複雜的查詢,對帶有GROUPBY、ORDERBY等操作的優化。

 

所以,針對SPJ和非SPJ的查詢優化,其實是對以上多種操作的優化。

“選擇”和“投影”操作,可以在關係代數規則的指導下進行優化。

表連線,需要多表連線的相關演算法完成優化。其他操作的優化多是基於索引和代價估算完成的。————物理優化。

 

邏輯查詢優化包括的技術:

1子查詢優化

2檢視重寫

3等價謂詞重寫

4條件化簡

5外連線消除

6巢狀連線消除

7連線消除

8語義優化

9非SPJ的優化

 

Query Execution Plan of MySQL

 

 

 

語法格式:

EXPLAIN[explain_type] explainable_stmt

 

可選項包括:

EXTENDED|PARTITIONS|FORMAT = format_name

format_name:

  TRADITIONAL|JSON

 

說明:

1 EXPLAIN命令,顯示SQL語句的查詢執行計劃。

2 EXPLAIN EXTENDED命令,顯示SQL語句的詳細的查詢執行計劃;之後可以通過“SHOW WARNINGS”命令檢視詳細的資訊。

3 EXPLAIN PARTITIONS命令。顯示SQL語句的帶有分割槽表資訊的查詢執行計劃。

4 EXPLAIN命令的輸出格式有兩種。

 4.1 TRADITIONAL;傳統型別;按行隔離,每個標識一個子操作

 4.2 JSOn;JSON格式。

5 explainable_stmt,可被EXPLAIN執行的SQL語句,包括的型別有:

SELECT、INSERT、UPDATE、DELETE。

 

執行順序

執行五表連線的查詢語句如下:

1

2

3

4

5

6

7

EXPLAIN

SELECT *

FROM(t1 LEFT JOIN t2 ON true),(t3 FULL JOIN t4 ON true),t5

WHERE id1=id2 AND

      id2=id3 AND

 id3=id4 AND

 id4=id5;

  008.png

 

 

結點解析

 

1)  id:每個被獨立執行的操作的標識,表示物件被操作的順序;id值大,先被執行;如果相同,執行順序從上到下。

2)  select_type:查詢中每個select子句的型別;

3)  table:名字,被操作物件的名稱,通常是表名,但有其他格式。

4)  partitions:匹配的分割槽資訊(對於非分割槽表值為NULL)。

5)  type:連線操作的型別;

6)  possible_keys:備選的索引(列出可能被使用到的索引)

7)  key:經優化器選定的索引;常用“ANALYZE TABLE”命令可以使優化器正確的選擇索引。

8)  key_len:被優化器選定的索引鍵的長度,單位是位元組。

9)  ref:表示本行被操作的物件的參照物件(被參照的物件可能是一個常用量“const”表示,也可能是其他的key指向的物件)。

10)              rows:查詢執行所掃描的元組個數(對於InnoDB,此值是個估計值)。

11)              filtered:按照條件表上資料被過濾的元組個數的百分比,“rows X filtered/100”可求出過濾後的元組數即實際的元組數。

 

子查詢的優化

 

 

 

當一個查詢是另一個查詢的子部分時,稱之為子查詢(查詢語句中巢狀有查詢語句)

 

查詢的子部分,包括哪些情況:

 

1目標列位置。

子查詢如果位於目標列,則只能是標量子查詢,否則資料庫可能返回類似“錯誤:子查詢必須只能返回一個欄位”的提示。

 

示例:

1

2

3

4

CREATE TABLE t1(k1 INT PRIMARY KEY,c1 INT);

 

CREATE TABLE t2(k2 INT PRIMARY KEY,c2 INT);

INSERT INTO t2 VALUES(1,10),(2,2),(3,30);

 

012.png

 

011.png

 

010.png

 

009.png

 

2 FORM字句位置

相關子查詢出現在FROM子句中,資料庫可能返回類似“在FROM子句中的子查詢無法參考相同查詢級別中的關係”的提示,所以相關子查詢不能出現在FROM子句中;

非相關子查詢出現在FROM子句中,可上拉子查詢到父層,在多表連線時統一考慮連線代價然後擇優。

 

示例:

 

014.png

013.png

 

3 WHERE子句位置

出現在WHERE子句中的子查詢,是一個條件表示式的一部分,而表示式可以分解為操作符和運算元;根據參與運算的不同的資料型別,操作符也不盡相同,如INT型別有“<、>、=、<>”等操作,這對子查詢均有一定的要求(如INT型的等值操作,要求查詢必須是標量子查詢)。另外,子查詢出現在WHERE子句中的格式,也有用謂詞指定的一些操作,如IN、BETWEEN、EXISTS等。

 

示例:

016.png

015.png

 

4 JOIN/ON子句位置

JOIN/ON子句可以拆分為兩部分,一是JOIN塊類似於FROM子句,二是ON子句塊類似於WHERE子句,這兩部分都可以出現子查詢。子查詢的處理方式同FROM子句和WHERE子句。

 

5 GROUPBY子句位置

目標列必須和GROUPBY關聯.可將子查詢寫在GROUPBY位置處,但子查詢用在GROUPBY處沒有實用意義。

 

6ORDERBY子句位置

可將子查詢寫在ORDERBY位置處,但ORDERBY操作是作用在整條SQL語句上的,子查詢用在ORDERBY處沒有實用意義。

 

 

子查詢的型別——從物件間的關係看:

 

相關子查詢

子查詢的執行依賴於外層父查詢的一些屬性值。子查詢因依賴於父查詢的引數,當父查詢的引數改變時,子查詢需要根據新引數值重新執行(查詢優化器對相關子查詢進行優化有一定意義),如:

017.png

 

非相關子查詢

子查詢的執行,不依賴於外層父查詢的任何屬性值。這樣子查詢具有獨立性,可獨自求解,形成一個子查詢計劃先於外層的查詢求解,如:

 018.png

 

 

子查詢的型別——從特定謂詞來看:

 

1 [NOT]IN/ALL/ANY/SOME子查詢

語義相近,表示“[取反]存在/所有/任何/任何”,左面是運算元,右面是子查詢,是最常見的子查詢型別之一。

 

2 [NOT]EXISTS子查詢

半連線語義,表示“[取反]存在”,沒有左運算元,右面是子查詢,也是最常見的子查詢型別之一。

 

3其他子查詢

除了上述兩種外的所有子查詢。

 

子查詢的型別——從語句的構成複雜程度來看:

 

1 SPJ子查詢

有選擇、連線、投影操作組成的查詢

 

2 GROUPBY子查詢

SPJ子查詢加上分組、聚集操作組成的查詢。

 

3其他子查詢

GROUPBY子查詢中加上其他子句如Top-N、LIMIT/OFFSET、集合、排序等操作。

 

後兩中子查詢有時合稱非SPJ查詢。

 

子查詢的型別——從結果的角度來看

 

標量子查詢

子查詢返回的結果集型別是一個簡單值(return a scalar, a single value)。

 

2單行單列子查詢

子查詢返回的結果集型別是零條或一條單元組(return a zero or single row, but only a column).相似於標量子查詢,但可能返回零條元組。

 

多行單列子查詢

子查詢返回的結果集型別是多條元組但只有一個簡單列(return multiple rows, but only a column)。

 

表子查詢

子查詢返回的結果集型別是一個表(多行多列)(return a table, one or more rows of one or more columns)。

 

為什麼要做子查詢優化?

 

在資料庫實現早期,查詢優化器對子查詢一般採用巢狀執行的方式,即父查詢中的每一行,都執行一次子查詢,這樣子查詢會執行很多次。這種執行方式效率低。

而對子查詢進行優化,可能帶來幾個數量級的查詢效率的提高。子查詢轉變成為連線操作之後,會得到如下好處:

1子查詢不用執行很多次。

2優化器可以根據統計資訊來選擇不同的連線方法和不同的連線順序。

 

子查詢中的連線條件、過濾條件分別變成了父查詢的連線條件、過濾條件,優化器可以對這些條件進行下推,以提高執行效率。

How to optimize SubQuery?

 

子查詢合併(SubQuery Coalescing

在某些條件下(語義等價:兩個查詢塊產生同樣的結果集),多個子查詢能夠合併成一個子查詢(合併後還是子查詢,以後可以通過其他技術消除掉子查詢)。這樣可以把多次表掃描、多次連線減少為單次表掃描和單次連線,如:

 

SELECT * FROM t1 WHERE a1<10 AND(

         EXISTS(SELECT a2 FROM t2 WHERE t2.a2<5 AND t2.b2=1)OR

        EXISTS(SELECT a2 FROM t2 WHERE t2.a2<5 AND t2.b2=2)

);

可優化為:

SELECT * FROM t1 WHERE a1<10 AND(

         EXISTS(SELECT a2 FROM t2 WHERE t2.a2<5 AND (t2.b2=1 OR t2.b2=2))

        /*兩個ESISTS子句合併為一個,條件也進行了合併*/

);

 

子查詢展開(SubQuery Unnesting

又稱為子查詢反巢狀,又稱為子查詢上拉。

把一些子查詢置於外層的父查詢中,作為連線關係與外層父查詢並列,其實質是把某些子查詢重寫為等價的多表連線操作(展開後,子查詢不存在了,外部查詢變成了多表連線)。

帶來的好處是,有關的訪問路徑、連線方法和連線順序可能被有效使用,使得查詢語句的層次儘可能地減少。

常見的IN/ANY/SOME/ALL/EXISTS依據情況準換為半連線(SEMI JOIN)、普通型別的子查詢消除等情況屬於此類,如:

 

SELECT * FROM t1,(SELECT * FROM t2 WHERE t2.a2>10) v_t2

WHERE t1.a1<10 AND v_t2.a2<20;

可優化為:

SELECT * FROM t1,t2 WHERE t1.a1<10 AND t2.a2<20 AND t2.a2>10;

/*子查詢變為了t1、t2表的連線操作,相當於把t2表從子查詢中上拉了一層*/

 

聚集子查詢消除(Aggregate SubQuery Elimination

通常,一些系統支援的是標量聚集子查詢消除。

如:

SELECT * FROM t1 WHERE t1.a1>(SELECT avg(t2.a2) FROM t2);

 

MySQL可以優化什麼格式的子查詢?

 

 

MySQl支援對簡單SELECT查詢中的子查詢優化,包括:

1 簡單SELECT查詢中的子查詢。

2 帶有DISTINCT、ORDERBY、LIMIT操作的簡單SELECT查詢中的子查詢。

 

CREATE TABLE t1 (a1 INT, b1 INT, PRIMARY KEY (a1));

CREATE TABLE t2 (a2 INT, b2 INT, PRIMARY KEY (a3));

CREATE TABLE t3 (a3 INT, b3 INT, PRIMARY KEY (a3));

插入10000行與上例同樣的資料。

 

查詢執行計劃如下:

mysql>EXPLAIN EXTENDED SELECT * FROM t1 WHERE t1.a1<100 AND a1 IN (SELECT a2 FROM t2 WHERE t2.a2>10);

 

019.jpg

 

MySQL不支援對如下情況的子查詢進行優化:

帶有UNION操作。

帶有GROUPBY、HAVING、聚集函式。

使用ORDERBY中帶有LIMIT。

內表、外表的個數超過MySQL支援的最大表的連線數。

 

聚集函式操作在子查詢中,查詢執行計劃如下:

 

020.jpg

 

子查詢合併技術,不支援:

 

mysql>explain extended select * from t1 where a1<4 and (exists (select a2 from t2 where t2.a2<5 and t2.b2=1) or exists(select a2 from t2 where t2.a2<5 and t2.b2=2));

 

022.jpg

021.jpg

 

子查詢展開(子查詢反巢狀)技術,支援的不夠好

 

mysql>explain extended select * from t1,(select * from t2 where t2.a2>10)v_t2 where t1.a1<10 and v_t2 .a2<20;

 

023.jpg

 

再看一個IN子查詢的例子,查詢執行計劃如下:

mysql>explain extended select * from t1 where ta.a1<100 and a1 in (select a2 from t2 where t2.a2>10);

 

024.jpg

025.jpg

 

……

 

聚集子查詢消除技術,不支援

 

mysql>explain extended select * from t1 where t1.a1>(select min(t2.a2) from t2);

 

026.jpg

 

Q:MySQL為什麼不支援聚集子查詢消除?

 

A:1 MySQL認為,聚集子查詢,只需要執行一次,得到結果後,即可把結果緩衝到記憶體中供後續連線或過濾等操作使用,沒有必要消除子查詢。

2另外,如果聚集子查詢在索引列上執行,則會更快得到查詢結果,更能加速查詢速度。

 

027.jpg

               

MySQL支援對哪些型別的子查詢進行優化?

 

示例1 MySQL不支援對EXISTS型別的子查詢做近一步的優化。

 

被查詢優化器處理後的語句為:

 

028.jpg

 

EXISTS型別的相關子查詢,查詢執行計劃如下:

mysql>explain extended select* from t1 where exists (select 1 from t2 where t1.a1=t2.a2 and t2.a2>10);

 029.jpg

 

 

示例2 MySQL不支援對NOT EXISTS型別的子查詢做進一步的優化。

 

被查詢優化器處理後的語句為:

 

030.jpg

 

NOT EXISTS型別的相關子查詢的查詢執行計劃如下:

mysql>explain extended select * from t1 where NOT EXISTS (select 1 from t2 where t1.a1=t2.a2 and t2.a2>10);

 031.jpg

 

示例3 MySQL支援對IN型別的子查詢的優化。

 

IN非相關子查詢,查詢計劃如下:

mysql>explain extended select * from t1 where t1.a1 IN (select a2 from t2 where t2.a2>10);

 

032.jpg

被查詢優化器處理後的語句為

 033.jpg

 

IN相關子查詢,查詢執行計劃如下:

mysql>explain extended select * from t1 where t1.a1 IN(select a2 from t2 where t1.a1=10);

 

034.jpg

 

被查詢優化器處理後的語句為:

 

035.jpg

 

示例4 MySQL支援對NOT IN型別的子查詢的優化。

 

NOT IN非相關子查詢,查詢計劃如下:

mysql>explain extended select * from t1 where t1.a1 NOT IN (select a2 from t2 where t2.a2>10);

 

036.jpg

 

被查詢優化器處理後的語句為

 

037.jpg

 

示例5 MySQL支援對ALL型別的子查詢的優化。

 

ALL非相關子查詢,查詢計劃如下:

mysql>explain extended select * from t1 where t1.a1 >ALL (select a2 from t2 where t2.a2>10);

 

038.jpg

 

被查詢優化器處理後的語句為

 

039.jpg

 

mysql>explain extended select * from t1 where t1.a1 =ALL (select a2 from t2 where t2.a2=10);

 

040.jpg

 

被查詢優化器處理後的語句為

 

041.jpg

 

mysql>explain extended select * from t1 where t1.a1 <ALL (select a2 from t2 where t2.a2=10);

 

042.jpg

 

被查詢優化器處理後的語句為

 

043.jpg

 

示例6 MySQL支援對SOME型別的子查詢的優化。

 

使用了“>SOME”式子的子查詢被優化,查詢計劃如下:

mysql>explain extended select * from t1 where t1.a1 >SOME (select a2 from t2 where t2.a2>10);

 

044.jpg

 

被查詢優化器處理後的語句為

 

045.jpg

使用了“=SOME”式子的子查詢被優化,查詢計劃如下:

mysql>explain extended select * from t1 where t1.a1 =SOME (select a2 from t2 where t2.a2=10);

 046.jpg

 

被查詢優化器處理後的語句為

 

047.jpg

 

使用了“<SOME”式子的子查詢被優化,查詢計劃如下:

mysql>explain extended select * from t1 where t1.a1 <SOME (select a2 from t2 where t2.a2=10);

 

048.jpg

 

被查詢優化器處理後的語句為

 

049.jpg

示例7 MySQL支援對ANY型別的子查詢的優化。

 

050.jpg

 

使用了“=ANY”式子的子查詢被優化,查詢計劃如下:

mysql>explain extended select * from t1 where t1.a1 =ANY (select a2 from t2 where t2.a2>10);

 

051.jpg

 

被查詢優化器處理後的語句為

 

052.jpg

 

使用了“<ANY”式子的子查詢被優化,查詢計劃如下:

mysql>explain extended select * from t1 where t1.a1 <ANY (select a2 from t2 where t2.a2>10);

 

053.jpg

 

被查詢優化器處理後的語句為

 

054.jpg