天天看點

子查詢

http://note.youdao.com/yws/public/redirect/share?id=50f2c387a5d6c9bc816c4b5282cf410e&type=false   1

一概述

MySQL子查詢優化的技術或優化政策,包括三種,分别為:

1 semi-join:半連接配接優化技術,本質上是把子查詢上拉到父查詢中,與父查詢的表做join/semi-join的操作。關鍵詞是“上拉”。

2 Materialization:物化子查詢,子查詢的結果通常緩存在記憶體或臨時表中

3 EXISTS strategy:把半連接配接轉換為EXISTS操作。本質上是把父表的條件下推到子查詢中關鍵詞是“下推”。

子查詢格式 可選的優化政策
IN/=ANY semi-join, Materialization,EXISTS strategy
NOT IN/<>ALL Materialization, EXISTS strategy

二 semi join

1 什麼是半連接配接?

semi join,半連接配接操作,是關系代數規定的擴充操作符之一。對于“R semi-join S”其語義為:連接配接後的結果中,隻包括R與S在公共屬性上的交集所限定的R中的部分元組。

anti join,反半連接配接,語義與半連接配接相反。即“R semi-join S”相當于“S anti-join R”。

2 為什麼要用半連接配接優化子查詢?

對于子查詢,其子查詢部分相對于父表的每個符合條件的元組,都要把子查詢執行一輪。效率低下。用半連接配接操作優化子查詢,是把子查詢上拉到父查詢中,這樣子查詢的表和父查詢中的表是并列關系,父表的每個符合條件的元組,隻需要在子表中找符合條件的元組即可,不需要“父表的每個符合條件的元組,都要把子查詢執行一輪”,是以效率提高。

這種優化方式,稱為“上拉/扁平化”。

3 半連接配接的優化政策

MySQL提供5種優化政策,來進一步優化半連接配接操作,分别是:

3.1 DUPS_WEEDOUT/重複剔除:執行普通的兩表内連接配接操作,用臨時表緩存結果,在臨時表中在所查詢的列上(a in subquery,MySQL自動在臨時表的a列上建立主鍵)通過主鍵去除重複的元組。在執行計劃中,可以看到“Start temporary/End temporary”。

參考資料:

https://mariadb.com/kb/en/mariadb/documentation/optimization-and-tuning/query-optimizations/optimization-strategies/duplicateweedout-strategy/

3.2 LOOSE_SCAN/松散掃描:在執行連接配接的時候,半連接配接的表S(R semi-join S)其元組需要有序(a in select b from t,b上存在索引,其元組的順序按照b成分組狀,則使用b上可用的索引讀取元組的時候,可以按序引序把相同的值的元組有序讀到),此時,根據索引拿出每組重複元組中的第一個元組(其他重複元組被讀到後跳過,是以要求S的元組有序),與R表進行連接配接。

https://mariadb.com/kb/en/mariadb/documentation/optimization-and-tuning/query-optimizations/optimization-strategies/loosescan-strategy/

3.3 FIRST_MATCH/首次比對:兩表做普通的内連接配接,連接配接後的結果,存于臨時表,在每次儲存到臨時表前,在臨時表中檢查是否有相同值的元組存在,不存在則儲存。

https://mariadb.com/kb/en/mariadb/documentation/optimization-and-tuning/query-optimizations/optimization-strategies/firstmatch-strategy/

3.4 MATERIALIZE_LOOKUP/索引式物化:把子查詢的結果物化到臨時表,執行連接配接的時候,可以用臨時表的索引(MySQL自動為臨時表建立索引)完成連接配接操作。這種情況下,完成連接配接的時候,通常被物化後的子查詢的結果是連接配接操作的内表,這樣才便于使用索引快速定位内表的元組。

3.5 MATERIALIZE_SCAN/掃描式物化:類似上一個。隻是臨時表的索引不能輔助加快連接配接,隻能通過全表掃描的方式,掃描臨時表中的元組,來完成半連接配接操作。這種情況下,完成連接配接的時候,通常被物化後的子查詢的結果是連接配接操作的外表,是以需要全表掃描。注意,兩種物化方式要求子查詢是“非相關的子查詢”,這樣其結果才穩定不變可被物化(記憶體化/緩存化)。

https://mariadb.com/kb/en/mariadb/documentation/optimization-and-tuning/query-optimizations/optimization-strategies/semi-join-materialization-strategy/

這5種政策的選擇,是通過代價估算的方式,來挑出其中最優的政策。

需要注意的是:

3.6半連接配接操作對于表達“半”含義的表,具有“存在即可”的含義,如果有多個元組符合連接配接條件,則不能讓每個符合條件的元組都與外表進行連接配接,是以,對于半連接配接的表即一個内表來說,就需要把滿足條件的重複元組去掉或使用索引等方式進行“隻選擇一個”式的操作。

3.7 如果需要更進一步學習,可以參考代碼中相關函數:

advance_sj_state()

semijoin_firstmatch_loosescan_access_paths()

semijoin_loosescan_fill_driving_table_position()

setup_semijoin_dups_elimination()

等等。

3.8 注意:半連接配接的這5種子優化政策,需要通過代價估算完成最優選擇。

4 MySQL對什麼樣的子查詢支援使用半連接配接進行優化?

子查詢語句必須滿足:

子查詢語句必須同時滿足(如果其中有一個不滿足,則采用EXISTS政策優化子查詢--這一點,就是SEMI-JOIN政策和EXISTS政策之間的差別。即據此知道對于子查詢,MySQL是如何決定使用哪種優化政策的):

4.1 謂詞必須是:IN/=ANY(不可以是NOT IN)

4.2 子查詢必須是一個簡單子查詢,不能包括:UNION/GROUP BY/HAVING/聚集函數。如果包含有ORDER BY則不可以帶有LIMIT子句。

4.3 表的總數(外表和内表之和)不能超過61(MySQL支援的最多可連接配接的表的個數)。

4.4 子查詢位于WHERE/JOIN-ON子句中,且首層不存在OR/NOT操作(即首層的條件子句中隻能是AND操作符連接配接的表達式。如果與OR操作在同層的子查詢不可以被半連接配接優化,但可以被“物化政策”優化)。

4.5 查詢塊中不可以包括:STRAIGHT_JOIN(與子查詢同層的連接配接子句中不可以包括STRAIGHT_JOIN)。

4.6 半連接配接參數必須打開(set optimizer_switch='semijoin=on';),否則采用EXISTS政策優化子查詢。

4.7 不是UPDATE/DELETE指令(在UPDATE/DELETE指令子查詢不被半連接配接優化)。

4.8 子查詢語句不能是無表子句(如子查詢形如“select 1”是不能被半連接配接優化的)。父查詢語句也不能是無表子句。

其他:

4.9 子查詢是相關子查詢或不相關子查詢均可。

4.10 可以帶有DISTINCT/LIMIT子句,但LIMIT不可以和ORDER BY合用。

5 半連接配接進一步優化為内連接配接

MySQL支援把子查詢優化為半連接配接, 還支援把優化後的半連接配接進一步優化為内連接配接。優化的條件,是子查詢的目标列,使用了主鍵或唯一鍵。例如,子查詢中的目标列是K2表的主鍵列:

CREATE TABLE K1 (pk1 INT PRIMARY KEY, a1 INT);

CREATE TABLE K2 (pk2 INT PRIMARY KEY, a2 INT);

INSERT INTO K1 VALUES (1,1), (2,2), (3,null);

INSERT INTO K2 VALUES (1,1), (2,2), (3,null);

SELECT * FROM K1 WHERE a1 IN (SELECT pk2 FROM K2);

mysql> EXPLAIN SELECT * FROM K1 WHERE a1 IN (SELECT pk2 FROM K2);

+----+-------------+-------+------------+--------+---------------+---------+---------+---------+------+----------+-------------+

| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |

| 1 | SIMPLE | K1 | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100.00 | Using where |

| 1 | SIMPLE | K2 | NULL | eq_ref | PRIMARY | PRIMARY | 4 | m.K1.a1 | 1 | 100.00 | Using index |

2 rows in set, 1 warning (0.00 sec)

mysql> show warnings;

+-------+------+--------------------------------------------------------------------------------------------------------------------------------------

| Level | Code | Message|

| Note | 1003 | /* select#1 */ select `m`.`k1`.`pk1` AS `pk1`,`m`.`k1`.`a1` AS `a1` from `m`.`k2` join `m`.`k1` where (`m`.`k2`.`pk2` = `m`.`k1`.`a1`) |

1 row in set (0.00 sec)

這個示例中,MySQL首先把子查詢優化為半連接配接(semi join),然後,調用pull_out_semijoin_tables()函數把半連接配接優化為内連接配接。其支援的形式為:

... WHERE oe IN (SELECT it1.primary_key WHERE p(it1, it2) ... )

謂詞p,隻能是内連接配接。

http://note.youdao.com/yws/public/redirect/share?id=a2cfee0ef4ad16f67e3d390fe7cf7029&type=false    2

三 物化政策

1 什麼是物化政策?

如果子查詢執行一次即可以得到結果,即子查詢的結果是穩定的,則這樣的子查詢可以被緩存起來,多次使用。緩存即是物化。緩存到記憶體中,如果記憶體中放不下,則會寫外存。在MySQL中,這個緩存對應的是臨時表(即:物化利用了臨時表的機制)。

相關子查詢依賴于父查詢,結果不确定,是以能被物化的,一定是“非相關子查詢”。

2 物化政策與半連接配接中的物化子政策有什麼不同嗎?

物化政策,outside-in materialization,取個名字叫“外化”。此政策隻用于“非相關子查詢”。這相當于把子查詢執行一次,然後緩存結果供多次使用。

而半連接配接中的物化子政策,盡管也是隻可以用于“非相關子查詢”,但優化後,查詢語句執行的是實實在在的内連接配接或半連接配接操作(如果子查詢的目标列存在唯一索引,則可以進一步把半連接配接優化為内連接配接操作,如前所述)。

如果子查詢不能被優化為半連接配接,則形如下式的(子查詢位于OR操作中),可以使用物化政策優化(主要在于OR操作,但非OR的IN子查詢,也可能被物化,但屬于半連接配接操作的物化)。

SELECT ... FROM ... WHERE (expr1, ..., exprN) [NOT] IN (SELECT ... ) OR expr;



四 EXISTS政策

1 什麼是EXISTS政策?

EXISTS政策,首先對應于SEMI-JOIN政策。其主要表述的,是下推的含義。下推什麼呢?下推父查詢的條件部分到子查詢的條件部分。其形式如下:

初始格式:

outer_expr IN (SELECT inner_expr FROM ... WHERE subquery_where)

下推後的格式:

EXISTS (SELECT 1 FROM ... WHERE subquery_where AND outer_expr=inner_expr)

這樣做的好處,是起到當子查詢不可消除的時候、用父查詢的條件在子查詢中起到限制子查詢元組個數的作用。



2 EXISTS政策的限制?

并不是所有的子查詢,都可以使用EXISTS政策,因為下推條件會帶來一些問題。這個問題就是對NULL值的處理。

滿足如下條件的,可以用下推來優化子查詢:

2.1 outer_expr和inner_expr都不可以有NULL值,且

2.2 SQL語句的語義,并不需要從子查詢的傳回結果(下推後的子查詢的傳回值,隻能是FALSE或TRUE)為FALSE的情況下區分NULL值。



如果不能全部滿足上述2個條件,MySQL會“下推”父查詢的條件到子查詢,但下推後的格式變化為:

情況一: outer_expr不可為NULL

EXISTS (SELECT 1 FROM ... WHERE subquery_where AND

(outer_expr=inner_expr OR inner_expr IS NULL))

情況二:outer_expr可為NULL,不可以下推。      

http://note.youdao.com/yws/public/redirect/share?id=0dd0f5b1bbd96d65ee4d5d93c732dfa6&type=false  3

五 進一步挖掘

1 MySQL僅僅是用半連接配接/物化/EXISTS政策來優化子查詢的嗎?

Convert the subquery to a join, or use table pullout and run the query as an inner join between subquery tables and outer tables. Table pullout pulls a table out from the subquery to the outer query.

這表明MySQL是可以把子查詢優化為連接配接操作的(這是一種特例:把半連接配接進一步優化為連接配接,需要子查詢的目标列為唯一鍵,如outer_expr IN (SELECT field_primary_key...))。

2 三種優化政策之間的關系

2.1 預設使用半連接配接進行優化,可以使用“set optimizer_switch='semijoin=off';”指令關閉半連接配接政策。

2.2 如果半連接配接參數關閉,則可以使用物化政策或EXISTS政策,使用哪種,由decide_subquery_strategy()函數決定,此函數調用compare_costs_of_subquery_strategies()函數進行兩種政策的代價計算,決定選用代價小的優化政策。可以使用“set optimizer_switch='materialization=off'; ”指令關閉物化政策,進而訓示MySQL優化器使用EXISTS政策。

3 如何區分是使用了哪種優化政策?

政策名稱 查詢執行計劃顯示項 說明
半連接配接政策 SHOW WARNINGS指令顯示内容 semi join (子查詢的select列為主鍵或者唯一索引)
物化政策 select_type SUBQUERY/SIMPLE (子查詢的select列有空值)
<materialize>(query fragment) 非半連接配接的物化,即物化政策中的物化
MATERIALIZED 被物化政策物化的子查詢(子查詢的select列沒有空值)
EXISTS政策 DEPENDENT SUBQUERY DEPENDENT說明了依賴的關系,EXISTS政策把子查詢變為了相關子查詢