資料庫查詢優化技術(二):子查詢優化
查詢的基本操作
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 |
|
結點解析
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 |
|
2 FORM字句位置
相關子查詢出現在FROM子句中,資料庫可能返回類似“在FROM子句中的子查詢無法參考相同查詢級別中的關係”的提示,所以相關子查詢不能出現在FROM子句中;
非相關子查詢出現在FROM子句中,可上拉子查詢到父層,在多表連線時統一考慮連線代價然後擇優。
示例:
3 WHERE子句位置
出現在WHERE子句中的子查詢,是一個條件表示式的一部分,而表示式可以分解為操作符和運算元;根據參與運算的不同的資料型別,操作符也不盡相同,如INT型別有“<、>、=、<>”等操作,這對子查詢均有一定的要求(如INT型的等值操作,要求查詢必須是標量子查詢)。另外,子查詢出現在WHERE子句中的格式,也有用謂詞指定的一些操作,如IN、BETWEEN、EXISTS等。
示例:
4 JOIN/ON子句位置
JOIN/ON子句可以拆分為兩部分,一是JOIN塊類似於FROM子句,二是ON子句塊類似於WHERE子句,這兩部分都可以出現子查詢。子查詢的處理方式同FROM子句和WHERE子句。
5 GROUPBY子句位置
目標列必須和GROUPBY關聯.可將子查詢寫在GROUPBY位置處,但子查詢用在GROUPBY處沒有實用意義。
6ORDERBY子句位置
可將子查詢寫在ORDERBY位置處,但ORDERBY操作是作用在整條SQL語句上的,子查詢用在ORDERBY處沒有實用意義。
子查詢的型別——從物件間的關係看:
1 相關子查詢
子查詢的執行依賴於外層父查詢的一些屬性值。子查詢因依賴於父查詢的引數,當父查詢的引數改變時,子查詢需要根據新引數值重新執行(查詢優化器對相關子查詢進行優化有一定意義),如:
2 非相關子查詢
子查詢的執行,不依賴於外層父查詢的任何屬性值。這樣子查詢具有獨立性,可獨自求解,形成一個子查詢計劃先於外層的查詢求解,如:
子查詢的型別——從特定謂詞來看:
1 [NOT]IN/ALL/ANY/SOME子查詢
語義相近,表示“[取反]存在/所有/任何/任何”,左面是運算元,右面是子查詢,是最常見的子查詢型別之一。
2 [NOT]EXISTS子查詢
半連線語義,表示“[取反]存在”,沒有左運算元,右面是子查詢,也是最常見的子查詢型別之一。
3其他子查詢
除了上述兩種外的所有子查詢。
子查詢的型別——從語句的構成複雜程度來看:
1 SPJ子查詢
有選擇、連線、投影操作組成的查詢
2 GROUPBY子查詢
SPJ子查詢加上分組、聚集操作組成的查詢。
3其他子查詢
GROUPBY子查詢中加上其他子句如Top-N、LIMIT/OFFSET、集合、排序等操作。
後兩中子查詢有時合稱非SPJ查詢。
子查詢的型別——從結果的角度來看
1 標量子查詢
子查詢返回的結果集型別是一個簡單值(return a scalar, a single value)。
2單行單列子查詢
子查詢返回的結果集型別是零條或一條單元組(return a zero or single row, but only a column).相似於標量子查詢,但可能返回零條元組。
3 多行單列子查詢
子查詢返回的結果集型別是多條元組但只有一個簡單列(return multiple rows, but only a column)。
4 表子查詢
子查詢返回的結果集型別是一個表(多行多列)(return a table, one or more rows of one or more columns)。
為什麼要做子查詢優化?
在資料庫實現早期,查詢優化器對子查詢一般採用巢狀執行的方式,即父查詢中的每一行,都執行一次子查詢,這樣子查詢會執行很多次。這種執行方式效率低。
而對子查詢進行優化,可能帶來幾個數量級的查詢效率的提高。子查詢轉變成為連線操作之後,會得到如下好處:
1子查詢不用執行很多次。
2優化器可以根據統計資訊來選擇不同的連線方法和不同的連線順序。
子查詢中的連線條件、過濾條件分別變成了父查詢的連線條件、過濾條件,優化器可以對這些條件進行下推,以提高執行效率。
How to optimize SubQuery?
1 子查詢合併(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子句合併為一個,條件也進行了合併*/
);
2 子查詢展開(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表從子查詢中上拉了一層*/
3 聚集子查詢消除(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);
MySQL不支援對如下情況的子查詢進行優化:
帶有UNION操作。
帶有GROUPBY、HAVING、聚集函式。
使用ORDERBY中帶有LIMIT。
內表、外表的個數超過MySQL支援的最大表的連線數。
聚集函式操作在子查詢中,查詢執行計劃如下:
子查詢合併技術,不支援:
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));
子查詢展開(子查詢反巢狀)技術,支援的不夠好
mysql>explain extended select * from t1,(select * from t2 where t2.a2>10)v_t2 where t1.a1<10 and v_t2 .a2<20;
再看一個IN子查詢的例子,查詢執行計劃如下:
mysql>explain extended select * from t1 where ta.a1<100 and a1 in (select a2 from t2 where t2.a2>10);
……
聚集子查詢消除技術,不支援
mysql>explain extended select * from t1 where t1.a1>(select min(t2.a2) from t2);
Q:MySQL為什麼不支援聚集子查詢消除?
A:1 MySQL認為,聚集子查詢,只需要執行一次,得到結果後,即可把結果緩衝到記憶體中供後續連線或過濾等操作使用,沒有必要消除子查詢。
2另外,如果聚集子查詢在索引列上執行,則會更快得到查詢結果,更能加速查詢速度。
MySQL支援對哪些型別的子查詢進行優化?
示例1 MySQL不支援對EXISTS型別的子查詢做近一步的優化。
被查詢優化器處理後的語句為:
EXISTS型別的相關子查詢,查詢執行計劃如下:
mysql>explain extended select* from t1 where exists (select 1 from t2 where t1.a1=t2.a2 and t2.a2>10);
示例2 MySQL不支援對NOT EXISTS型別的子查詢做進一步的優化。
被查詢優化器處理後的語句為:
NOT EXISTS型別的相關子查詢的查詢執行計劃如下:
mysql>explain extended select * from t1 where NOT EXISTS (select 1 from t2 where t1.a1=t2.a2 and t2.a2>10);
示例3 MySQL支援對IN型別的子查詢的優化。
IN非相關子查詢,查詢計劃如下:
mysql>explain extended select * from t1 where t1.a1 IN (select a2 from t2 where t2.a2>10);
被查詢優化器處理後的語句為
IN相關子查詢,查詢執行計劃如下:
mysql>explain extended select * from t1 where t1.a1 IN(select a2 from t2 where t1.a1=10);
被查詢優化器處理後的語句為:
示例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);
被查詢優化器處理後的語句為
示例5 MySQL支援對ALL型別的子查詢的優化。
ALL非相關子查詢,查詢計劃如下:
mysql>explain extended select * from t1 where t1.a1 >ALL (select a2 from t2 where t2.a2>10);
被查詢優化器處理後的語句為
mysql>explain extended select * from t1 where t1.a1 =ALL (select a2 from t2 where t2.a2=10);
被查詢優化器處理後的語句為
mysql>explain extended select * from t1 where t1.a1 <ALL (select a2 from t2 where t2.a2=10);
被查詢優化器處理後的語句為
示例6 MySQL支援對SOME型別的子查詢的優化。
使用了“>SOME”式子的子查詢被優化,查詢計劃如下:
mysql>explain extended select * from t1 where t1.a1 >SOME (select a2 from t2 where t2.a2>10);
被查詢優化器處理後的語句為
使用了“=SOME”式子的子查詢被優化,查詢計劃如下:
mysql>explain extended select * from t1 where t1.a1 =SOME (select a2 from t2 where t2.a2=10);
被查詢優化器處理後的語句為
使用了“<SOME”式子的子查詢被優化,查詢計劃如下:
mysql>explain extended select * from t1 where t1.a1 <SOME (select a2 from t2 where t2.a2=10);
被查詢優化器處理後的語句為
示例7 MySQL支援對ANY型別的子查詢的優化。
使用了“=ANY”式子的子查詢被優化,查詢計劃如下:
mysql>explain extended select * from t1 where t1.a1 =ANY (select a2 from t2 where t2.a2>10);
被查詢優化器處理後的語句為
使用了“<ANY”式子的子查詢被優化,查詢計劃如下:
mysql>explain extended select * from t1 where t1.a1 <ANY (select a2 from t2 where t2.a2>10);
被查詢優化器處理後的語句為