MySQL 8.0 參考手冊——8.2優化 SQL 語句

數據庫應用程序的核心邏輯是通過 SQL 語句來執行的,無論是通過解釋器直接發出還是通過 API 在後臺提交。本節中的調整指南有助於提高各種 MySQL 應用程序的速度。指南涵蓋讀寫數據的 SQL 操作、一般 SQL 操作的幕後開銷,以及數據庫監控等特定場景中使用的操作。

一、 優化 SELECT 語句

8.2.1.1 優化 WHERE 子句

本節討論在處理 WHERE 子句時可以進行的優化。示例使用的是 SELECT 語句,但同樣的優化也適用於 DELETE 和 UPDATE 語句中的 WHERE 子句。

你可能會受到誘惑,爲了加快算術運算的速度,而重寫查詢,犧牲可讀性。但由於MySQL會自動執行類似的優化,你通常可以避免這項工作,並讓查詢保持更易於理解和維護的形式。MySQL執行的一些優化包括:

刪除不必要的括號:

   ((a AND b) AND c OR (((a AND b) AND (c AND d))))
-> (a AND b AND c) OR (a AND b AND c AND d)

常數合併:

   (a<b AND b=c) AND a=5
-> b>5 AND b=c AND a=5

常數條件移除:

   (b>=5 AND b=5) OR (b=6 AND 5=5) OR (b=7 AND 5=6)
-> b=5 OR b=6

索引使用的常量表達式只評估一次。

從 MySQL 8.0.16 開始,數字類型列與常量值的比較會進行檢查,刪除無效或過時的值:

# CREATE TABLE t (c TINYINT UNSIGNED NOT NULL);
  SELECT * FROM t WHERE c ≪ 256;
-SELECT * FROM t WHERE 1;

對於 MyISAM 表和 MEMORY 表,將直接從表信息中獲取單個表不帶 WHERE條件 的 COUNT(*)。如果只對一個表使用任何 NOT NULL 表達式,也會這樣做。

早期檢測無效常量表達式。MySQL 能快速檢測到某些 SELECT 語句是不可能的,因此不返回任何記錄。

如果不使用 GROUP BY 或聚合函數(COUNT()、MIN() 等),HAVING 將與 WHERE 合併。

對於連接中的每個表,都會構造一個更簡單的 WHERE,以便快速評估表的 WHERE 值,並儘快跳過記錄。

首先讀取所有常量表,然後纔讀取查詢中的其他表。常量表是指以下任何一種表:

1,空表或只有一行的表。

2,在 PRIMARY KEY 或 UNIQUE 索引上使用 WHERE 子句的表,所有索引部分都與常量表達式比較,並定義爲 NOT NULL。

以下所有表格均用作常量表:

SELECT * FROM t WHERE primary_key=1;
SELECT * FROM t1,t2
  WHERE t1.primary_key=1 AND t2.primary_key=t1.id;

通過嘗試所有可能性,可以找到連接表的最佳連接組合。如果 ORDER BY 和 GROUP BY 子句中的所有列都來自同一張表,則在連接時優先選擇該表。

如果存在 ORDER BY 子句和不同的 GROUP BY 子句,或者 ORDER BY 或 GROUP BY 包含來自連接隊列中第一個表以外的表的列,則會創建一個臨時表。

如果使用 SQL_SMALL_RESULT 修改器,MySQL 將使用內存中的臨時表。

每個表的索引都會被查詢,除非優化器認爲使用表掃描更有效,否則會使用最佳索引。曾經,使用掃描的依據是最佳索引是否跨越表的 30% 以上,但使用索引還是掃描不再由一個固定的百分比來決定。現在的優化器更加複雜,會根據表大小、行數和 I/O 塊大小等其他因素進行估計。

在某些情況下,MySQL 可以從索引中讀取記錄,甚至無需查閱數據文件。如果從索引中使用的所有列都是數字列,則只使用索引樹來解決查詢。

在輸出每一行之前,會跳過與 HAVING 子句不匹配的記錄。

快速查詢的一些示例:

SELECT COUNT(*) FROM tbl_name;

SELECT MIN(key_part1),MAX(key_part1) FROM tbl_name;

SELECT MAX(key_part2) FROM tbl_name
  WHERE key_part1=constant;

SELECT ... FROM tbl_name
  ORDER BY key_part1,key_part2,... LIMIT 10;

SELECT ... FROM tbl_name
  ORDER BY key_part1 DESC, key_part2 DESC, ... LIMIT 10;

假設索引列是數字列,MySQL 只使用索引樹解決以下查詢:

SELECT key_part1,key_part2 FROM tbl_name WHERE key_part1=val;

SELECT COUNT(*) FROM tbl_name
  WHERE key_part1=val1 AND key_part2=val2;

SELECT MAX(key_part2) FROM tbl_name GROUP BY key_part1;

以下查詢使用索引檢索按排序順序排列的行,而無需單獨的排序過程:

SELECT ... FROM tbl_name
  ORDER BY key_part1,key_part2,... ;

SELECT ... FROM tbl_name
  ORDER BY key_part1 DESC, key_part2 DESC, ... ;

 

8.2.1.2 範圍優化

範圍訪問方法使用單個索引來檢索包含在一個或多個索引值區間內的錶行子集。它可用於單部分或多字段索引。下文將介紹優化程序使用範圍訪問的條件。

  • 單字段索引的範圍訪問方法
  • 多字段索引的範圍訪問方法
  • 多值比較的相等範圍優化
  • 跳過掃描範圍訪問方法
  • 行構造函數表達式的範圍優化
  • 限制範圍優化的內存使用
單字段索引的範圍訪問方法

對於單字段索引,索引值區間可以方便地用 WHERE 子句中的相應條件來表示,表示爲範圍條件而不是 "區間"。

單字段索引的範圍條件定義如下:

  • 對於 BTREE 和 HASH 索引,在使用 =、<=>、IN()、IS NULL 或 IS NOT NULL 操作符時,鍵部分與常量值的比較是範圍條件。
  • 此外,對於 BTREE 索引,在使用 >、<、>=、<=、BETWEEN、!= 或 <> 操作符或 LIKE 比較(如果 LIKE 的參數是不以通配符開頭的常量字符串)時,鍵部分與常量值的比較是範圍條件。
  • 對於所有索引類型,多個範圍條件與 OR 或 AND 結合在一起構成一個範圍條件。

上述說明中的 "常量值 "是指以下其中之一:

  • 查詢字符串中的常數
  • 來自同一連接的常量表或系統表的一列
  • 不相關子查詢的結果
  • 完全由上述類型的子表達式組成的表達式

下面是一些在 WHERE 子句中使用範圍條件的查詢示例:

SELECT * FROM t1
  WHERE key_col > 1
  AND key_col < 10;

SELECT * FROM t1
  WHERE key_col = 1
  OR key_col IN (15,18,20);

SELECT * FROM t1
  WHERE key_col LIKE 'ab%'
  OR key_col BETWEEN 'bar' AND 'foo';

在優化器常量傳播階段,一些非常量值可能會被轉換爲常量。

MySQL 會嘗試從 WHERE 子句中爲每個可能的索引提取範圍條件。在提取過程中,不能用於構建範圍的條件會被丟棄,產生重疊範圍的條件會被合併,產生空範圍的條件會被刪除。

請看下面的語句,其中 key1 是有索引的列,而 nonkey 沒有索引:

SELECT * FROM t1 WHERE
  (key1 < 'abc' AND (key1 LIKE 'abcde%' OR key1 LIKE '%b')) OR
  (key1 < 'bar' AND nonkey = 4) OR
  (key1 < 'uux' AND key1 > 'z');

key1 的提取過程如下:

1,從原始 WHERE 子句開始:

(key1 < 'abc' AND (key1 LIKE 'abcde%' OR key1 LIKE '%b')) OR
(key1 < 'bar' AND nonkey = 4) OR
(key1 < 'uux' AND key1 > 'z')

2,刪除 nonkey = 4 和 key1 LIKE '%b',因爲它們不能用於範圍掃描。正確的刪除方法是用 TRUE 替換它們,這樣我們在進行範圍掃描時就不會遺漏任何匹配的記錄。用 "true "替換它們的結果是

(key1 < 'abc' AND (key1 LIKE 'abcde%' OR TRUE)) OR
(key1 < 'bar' AND TRUE) OR
(key1 < 'uux' AND key1 > 'z')

3,摺疊條件總是爲真或爲假:

  • (key1 LIKE 'abcde%' OR TRUE) is always true

  • (key1 < 'uux' AND key1 > 'z') is always false

將這些條件替換爲常數即可得到:

(key1 < 'abc' AND TRUE) OR (key1 < 'bar' AND TRUE) OR (FALSE)

去掉不必要的 TRUE 和 FALSE 常量,結果是:

(key1 < 'abc') OR (key1 < 'bar')

4,將重疊的區間合併爲一個區間,就得到了用於範圍掃描的最終條件:

(key1 < 'bar')

一般來說(如前面的示例所示),範圍掃描使用的條件比 WHERE 子句的限制性要小。MySQL 會執行額外檢查,以過濾掉滿足範圍條件但不滿足完整 WHERE 子句的記錄。

範圍條件提取算法可以處理任意深度的嵌套 AND/OR 結構,其輸出與 WHERE 子句中條件出現的順序無關。

對於空間索引的範圍訪問方法,MySQL 不支持合併多個範圍。要繞過這一限制,可以使用 UNION 和相同的 SELECT 語句,只是將每個空間謂詞放在不同的 SELECT 中。

 多字段索引的範圍訪問方法

多字段索引的範圍條件是單字段索引範圍條件的擴展。多字段索引的範圍條件限制索引行位於一個或多個關鍵元組區間內。關鍵元組區間是在一組關鍵元組上定義的,使用的是索引的排序。

例如,考慮一個定義爲 key1(key_part1、key_part2、key_part3)的多字段索引,以及以下按 key的順序排列的鍵元組:

key_part1  key_part2  key_part3
  NULL       1          'abc'
  NULL       1          'xyz'
  NULL       2          'foo'
   1         1          'abc'
   1         1          'xyz'
   1         2          'abc'
   2         1          'aaa'

條件 key_part1 = 1 定義了這個區間:

(1,-inf,-inf) <= (key_part1,key_part2,key_part3) < (1,+inf,+inf)

該區間涵蓋了前面數據集中的第 4、5 和 6 行,可用於範圍訪問方法。

相比之下,key_part3 = 'abc'條件沒有定義單個區間,因此不能用於範圍訪問方法。

下面的描述將詳細說明範圍條件如何在多字段索引中起作用。

對於 HASH 索引,可以使用包含相同值的每個區間。這意味着只能爲以下形式的條件創建區間:

    key_part1 cmp const1
AND key_part2 cmp const2
AND ...
AND key_partN cmp constN;

這裏,const1、const2、...是常數,cmp 是 =、<=> 或 IS NULL 比較操作符之一,條件涵蓋所有索引部分。(例如,下面是一個由三部分組成的 HASH 索引的範圍條件:

key_part1 = 1 AND key_part2 IS NULL AND key_part3 = 'foo'

對於 BTREE 索引,區間可用於與 AND 結合使用的條件,其中每個條件使用 =、<=>、IS NULL、>、<、>=、<=、!=、<>、BETWEEN 或 LIKE "模式"(其中 "模式 "不以通配符開頭)比較關鍵部分和常量值。只要能確定一個包含符合條件的所有行的單鍵元組(如果使用 <> 或 != 則爲兩個區間),就可以使用區間。

只要比較操作符是 =、<=> 或 IS NULL,優化器就會嘗試使用其他關鍵部分來確定區間。如果運算符爲 >、<、>=、<=、!=、<>、BETWEEN 或 LIKE,優化器將使用它,但不再考慮其他關鍵部分。在下面的表達式中,優化器使用了第一次比較中的 =。它還使用了第二次比較中的 >=,但不再考慮其他關鍵部分,也不使用第三次比較來構建區間:

key_part1 = 'foo' AND key_part2 >= 10 AND key_part3 > 10

單一區間爲:

('foo',10,-inf) < (key_part1,key_part2,key_part3) < ('foo',+inf,+inf)

創建的區間可能包含比初始條件更多的行。例如,前一個區間包含的值 ('foo', 11, 0) 並不滿足初始條件。

如果將涵蓋區間內所包含的行集的條件與 OR 結合在一起,它們就會形成一個條件,涵蓋其區間結合處所包含的行集。如果將這些條件與 AND 結合在一起,則會形成一個條件,覆蓋包含在其區間的交集內的行集。例如,對於這個由兩部分組成的索引的條件:

(key_part1 = 1 AND key_part2 < 2) OR (key_part1 > 5)

這些區間是:

(1,-inf) < (key_part1,key_part2) < (1,2)
(5,-inf) < (key_part1,key_part2)

在本例中,第一行的區間左邊界使用一個關鍵部分,右邊界使用兩個關鍵部分。第二行的區間只使用了一個關鍵字部分。EXPLAIN 輸出中的 key_len 列表示所使用的關鍵字前綴的最大長度。

在某些情況下,key_len 可能表示使用了一個關鍵字部分,但這可能與你的預期不同。假設 key_part1 和 key_part2 可以是 NULL。那麼 key_len 列會顯示以下條件下的兩個鍵部分長度:

key_part1 >= 1 AND key_part2 < 2

但事實上,條件是這樣轉換的:

key_part1 >= 1 AND key_part2 IS NOT NULL
多值比較的相等範圍優化

考慮這些表達式,其中 col_name 是索引列:

col_name IN(val1, ..., valN)
col_name = val1 OR ... OR col_name = valN

如果 col_name 等於多個值中的任意一個,則每個表達式爲真。這些比較是相等範圍比較(其中 "範圍 "是單個值)。優化器會按以下方式估算爲相等範圍比較讀取合格記錄的成本:

如果 col_name 上有唯一索引,則每個範圍的行估計值爲 1,因爲最多隻能有一條記錄具有給定值。

否則,col_name 上的任何索引都是非唯一的,優化器可以通過深入索引或索引統計來估算每個範圍的行數。

通過索引潛入,優化器會在範圍的兩端進行潛入,並使用範圍內的行數作爲估計值。例如,表達式 col_name IN (10, 20, 30) 有三個相等範圍,優化器會在每個範圍進行兩次下潛,以生成行估計值。每一對下潛都會產生具有給定值的行數估計值。

索引潛入可提供精確的行估計,但隨着表達式中比較值數量的增加,優化器生成行估計所需的時間也會延長。使用索引統計的準確性不如索引潛入,但可以更快地對大數值列表進行行估計。

通過 eq_range_index_dive_limit 系統變量,可以配置優化器從一種行估計策略切換到另一種行估計策略的值數。要允許在最多 N 個相等範圍的比較中使用索引下潛,請將 eq_range_index_dive_limit 設置爲 N + 1。如果要禁用統計信息並始終使用索引下潛,而不管 N 多 少,則應將 eq_range_index_dive_limit 設置爲 0。

要更新表索引統計以獲得最佳估計,請使用 ANALYZE TABLE。

在 MySQL 8.0 之前,除了使用 eq_range_index_dive_limit 系統變量外,無法跳過使用索引潛入來估計索引有用性。在 MySQL 8.0 中,滿足所有這些條件的查詢都可以跳過索引潛入:

  • 查詢針對的是單個表,而不是多個表的連接。
  • 存在單索引 FORCE INDEX 索引提示。這樣做的目的是,如果強制使用索引,則不會從執行索引潛入的額外開銷中獲得任何收益。
  • 索引是非唯一索引,不是 FULLTEXT 索引。
  • 不存在子查詢。
  • 沒有 DISTINCT、GROUP BY 或 ORDER BY 子句。

 對於 EXPLAIN FOR CONNECTION,如果跳過索引下潛,輸出結果會發生如下變化:

  • 對於傳統輸出,行數和過濾值均爲 NULL。
  • 對於 JSON 輸出,不會出現 rows_examined_per_scan 和 rows_produced_per_join,skip_index_dive_due_to_force 爲 true,成本計算也不準確。

在不使用 FOR CONNECTION 的情況下,當跳過索引潛入時,EXPLAIN 輸出不會發生變化。

在執行了跳過索引潛入的查詢後,信息模式 OPTIMIZER_TRACE 表中的相應行包含一個跳過索引潛入的索引_dives_for_range_access 值。

跳過掃描範圍訪問方法

請考慮以下情況:

CREATE TABLE t1 (f1 INT NOT NULL, f2 INT NOT NULL, PRIMARY KEY(f1, f2));
INSERT INTO t1 VALUES
  (1,1), (1,2), (1,3), (1,4), (1,5),
  (2,1), (2,2), (2,3), (2,4), (2,5);
INSERT INTO t1 SELECT f1, f2 + 5 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 10 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 20 FROM t1;
INSERT INTO t1 SELECT f1, f2 + 40 FROM t1;
ANALYZE TABLE t1;

EXPLAIN SELECT f1, f2 FROM t1 WHERE f2 > 40;

要執行此查詢,MySQL 可以選擇索引掃描來獲取所有記錄(索引包括要選擇的所有列),然後應用 WHERE 子句中的 f2 > 40 條件來生成最終結果集。

範圍掃描比全索引掃描更有效,但在本例中不能使用,因爲在第一個索引列 f1 上沒有條件。不過,從 MySQL 8.0.13 開始,優化器可以執行多次範圍掃描,對 f1 的每個值都執行一次,使用的方法稱爲跳過掃描,與鬆散索引掃描類似

  1. 跳過第一個索引部分 f1(索引前綴)的不同值。
  2. 針對剩餘索引部分中 f2 > 40 條件的每個不同前綴值執行子範圍掃描。

對於前面顯示的數據集,算法是這樣運行的:

  1. 獲取第一個關鍵字部分的第一個不同值 (f1 = 1)。
  2. 根據第一個和第二個關鍵字部分構建範圍(f1 = 1 AND f2 > 40)。
  3. 執行範圍掃描。
  4. 獲取第一個關鍵字部分的下一個不同值 (f1 = 2)。
  5. 根據第一個和第二個關鍵字部分構建範圍(f1 = 2 AND f2 > 40)。
  6. 執行範圍掃描。

使用此策略可減少訪問記錄的數量,因爲 MySQL 會跳過不符合每個構造範圍的記錄。這種跳過掃描訪問方法適用於以下條件:

  • 表 T 至少有一個複合索引,其關鍵部分的形式爲([A_1, ..., A_k,]B_1, ..., B_m,C[, D_1, ..., D_n])。關鍵部分 A 和 D 可以爲空,但 B 和 C 必須爲非空。
  • 查詢只引用一個表。
  • 查詢未使用 GROUP BY 或 DISTINCT。
  • 查詢只引用索引中的列。
  • 關於 A_1、...、A_k 的謂詞必須是相等謂詞,並且必須是常量。這包括 IN() 操作符。
  • 查詢必須是連接查詢,即 OR 條件的 AND:(cond1(key_part1) OR cond2(key_part1)) AND (cond1(key_part2) OR ...) AND ...
  • C 列上必須有一個範圍條件。
  • 允許在 D 列上設置條件。D 列上的條件必須與 C 列上的範圍條件相結合。

跳轉掃描的使用在 EXPLAIN 輸出中顯示如下:

  • 在 Extra 列中使用索引進行跳過掃描表示使用了鬆散索引跳過掃描訪問方法。
  • 如果索引可用於跳過掃描,則索引應在 possible_keys 列中可見。

在優化器跟蹤輸出中,"skip scan"(跳過掃描)元素以這種形式表示使用了跳過掃描:

"skip_scan_range": {
  "type": "skip_scan",
  "index": index_used_for_skip_scan,
  "key_parts_used_for_access": [key_parts_used_for_access],
  "range": [range]
}

您可能還會看到一個 "best_skip_scan_summary "元素。如果選擇跳過掃描作爲最佳範圍訪問變量,則會寫入 "chosen_range_access_summary"。如果選擇跳過掃描作爲總體的最佳訪問方法,則會出現一個 "best_access_path "元素。

跳過掃描的使用取決於 optimizer_switch 系統變量的 skip_scan 標誌值。要禁用它,請將 skip_scan 設置爲 off。

除了使用 optimizer_switch 系統變量控制優化器在整個會話範圍內使用 Skip Scan 之外,MySQL 還支持優化器提示,以便在每條語句的基礎上影響優化器。

行構造表達式的範圍優化

優化器可以對這種形式的查詢應用範圍掃描訪問方法:

SELECT ... FROM t1 WHERE ( col_1, col_2 ) IN (( 'a', 'b' ), ( 'c', 'd' ));

以前,要使用範圍掃描,必須將查詢寫成這樣:

SELECT ... FROM t1 WHERE ( col_1 = 'a' AND col_2 = 'b' )
OR ( col_1 = 'c' AND col_2 = 'd' );

要讓優化器使用範圍掃描,查詢必須滿足這些條件:

  • 只使用 IN() 謂詞,不使用 NOT IN()。
  • 在 IN() 謂詞的左側,行構造函數只包含列引用。
  • 在 IN() 謂詞的右側,行構造函數只包含運行時常量,這些常量要麼是字面量,要麼是在執行過程中綁定到常量的本地列引用。
  • 在 IN() 謂詞的右側,有不止一個行構造函數。
限制內存使用以優化範圍

要控制範圍優化器的可用內存,請使用 range_optimizer_max_mem_size 系統變量:

  • 值爲 0 表示 "無限制"。
  • 如果值大於 0,優化器在考慮範圍訪問方法時會跟蹤消耗的內存。如果即將超過指定的限制,就會放棄範圍訪問方法,轉而考慮其他方法,包括全表掃描。這可能不是最佳選擇。如果發生這種情況,就會出現以下警告(其中 N 是當前 range_optimizer_max_mem_size 值):
    Warning    3170    Memory capacity of N bytes for
                       'range_optimizer_max_mem_size' exceeded. Range
                       optimization was not done for this query.

    對於 UPDATE 和 DELETE 語句,如果優化器退回到全表掃描,並且啓用了 sql_safe_updates 系統變量,則會出現錯誤而不是警告,因爲實際上沒有使用鍵來確定要修改哪些行。

如果個別查詢超出了可用的範圍優化內存,優化器會退回到較差的優化計劃,那麼增加 range_optimizer_max_mem_size 值可能會提高性能。

要估算處理範圍表達式所需的內存量,請使用以下指南:

對於像下面這樣的簡單查詢(其中有一個候選鍵用於範圍訪問方法),每個謂詞與 OR 結合使用約爲 230 字節:

SELECT COUNT(*) FROM t
WHERE a=1 OR a=2 OR a=3 OR .. . a=N;

同樣,對於像下面這樣的查詢,每個謂詞與 AND 結合使用大約 125 個字節:

SELECT COUNT(*) FROM t
WHERE a=1 AND b=1 AND c=1 ... N;

對於包含 IN() 謂詞的查詢,可以使用"...... "查詢:

SELECT COUNT(*) FROM t
WHERE a IN (1,2, ..., M) AND b IN (1,2, ..., N);

IN() 列表中的每個字面值都算作一個與 OR 結合的謂詞。如果有兩個 IN() 列表,則與 OR 結合的謂詞個數是每個列表中字面值個數的乘積。因此,上述情況中與 OR 結合的謂詞個數爲 M × N。

8.2.1.3 索引合併優化

 索引合併訪問方法通過多個範圍掃描檢索記錄,並將其結果合併爲一個結果。這種訪問方法只合並單個表的索引掃描,不合並多個表的掃描。合併可以產生底層掃描的聯合、交叉或交叉聯合。
可使用索引合併的查詢示例:

SELECT * FROM tbl_name WHERE key1 = 10 OR key2 = 20;

SELECT * FROM tbl_name
  WHERE (key1 = 10 OR key2 = 20) AND non_key = 30;

SELECT * FROM t1, t2
  WHERE (t1.key1 IN (1,2) OR t1.key2 LIKE 'value%')
  AND t2.key1 = t1.some_col;

SELECT * FROM t1, t2
  WHERE t1.key1 = 1
  AND (t2.key1 = t1.some_col OR t2.key2 = t1.some_col2);

索引合併優化算法有以下已知限制:

如果您的查詢有一個複雜的 WHERE 子句,其中包含較深的 AND/OR 嵌套,而 MySQL 沒有選擇最佳計劃,請嘗試使用以下身份轉換來分配子句:

(x AND y) OR z => (x OR z) AND (y OR z)
(x OR y) AND z => (x AND z) OR (y AND z)

索引合併不適用於全文索引。

在 EXPLAIN 輸出中,索引合併方法在類型列中顯示爲 index_merge。在這種情況下,key 列包含使用的索引列表,key_len 包含這些索引的最長鍵部分列表。

索引合併訪問方法有多種算法,這些算法顯示在 EXPLAIN 輸出的 Extra 字段中:

  • Using intersect(...)
  • Using union(...)
  • Using sort_union(...)

下文將詳細介紹這些算法。優化器會根據各種可用選項的成本估算,在不同的索引合併算法和其他訪問方法中做出選擇。

  • 索引合併交叉訪問算法
  • 索引合併聯合訪問算法
  • 索引合併排序-聯合訪問算法
  • 影響索引合併優化的因素
索引合併交叉訪問算法

這種訪問算法適用於將 WHERE 子句轉換爲不同鍵上的多個範圍條件並結合 AND 的情況,且每個條件都是以下條件之一:

  • 這種形式的 N 部分表達式,其中索引正好有 N 部分(即所有索引部分都包括在內):
  • key_part1 = const1 AND key_part2 = const2 ... AND key_partN = constN
  • InnoDB 表主鍵上的任何範圍條件。
SELECT * FROM innodb_table
  WHERE primary_key < 10 AND key_col1 = 20;

SELECT * FROM tbl_name
  WHERE key1_part1 = 1 AND key1_part2 = 2 AND key2 = 2;

索引合併交集算法會對所有使用的索引執行同步掃描,並生成從合併索引掃描中獲得的行序列交集。

如果所使用的索引覆蓋了查詢中使用的所有列,則不會檢索到完整的錶行(在這種情況下,EXPLAIN 輸出包含 Extra 字段中的 Using index)。下面是這樣一個查詢示例:

SELECT COUNT(*) FROM t1 WHERE key1 = 1 AND key2 = 1;

如果使用的索引沒有覆蓋查詢中使用的所有列,則只有在滿足所有使用鍵的範圍條件時,纔會檢索到完整的行。

如果合併條件之一是 InnoDB 表主鍵的條件,則不會用於檢索記錄,而是用於過濾使用其他條件檢索的記錄。

索引合併聯合訪問算法

該算法的標準與索引合併交叉算法的標準類似。該算法適用於將表中的 WHERE 子句轉換爲不同鍵上的多個範圍條件,並與 OR 結合使用,且每個條件都是以下條件之一時:

這種形式的 N 部分表達式,其中索引正好有 N 部分(即所有索引部分都包括在內):

key_part1 = const1 OR key_part2 = const2 ... OR key_partN = constN

InnoDB 表主鍵上的任何範圍條件。

適用索引合併交叉算法的條件。

SELECT * FROM t1
  WHERE key1 = 1 OR key2 = 2 OR key3 = 3;

SELECT * FROM innodb_table
  WHERE (key1 = 1 AND key2 = 2)
     OR (key3 = 'foo' AND key4 = 'bar') AND key5 = 5;
索引合併排序聯合訪問算法

這種訪問算法適用於 WHERE 子句轉換爲由 OR 組合的多個範圍條件時,但不適用索引合併聯合算法。

SELECT * FROM tbl_name
  WHERE key_col1 < 10 OR key_col2 < 20;

SELECT * FROM tbl_name
  WHERE (key_col1 > 10 OR key_col2 = 20) AND nonkey_col = 30;

sort-union 算法與 union 算法的區別在於,sort-union 算法必須首先獲取所有行的 ID 並對其進行排序,然後才能返回任何行。

影響索引合併優化

索引合併的使用受優化器開關係統變量 index_merge、index_merge_intersection、index_merge_union 和 index_merge_sort_union 標誌值的限制。默認情況下,所有這些標誌都處於開啓狀態。要只啓用某些算法,可將 index_merge 設置爲關閉,並只啓用其他應允許的算法。

除了使用 optimizer_switch 系統變量控制優化器在整個會話範圍內使用索引合併算法外,MySQL 還支持優化器提示,以便在每條語句的基礎上影響優化器

 8.2.1.4 哈希連接優化

默認情況下,MySQL(8.0.18 及更高版本)會盡可能使用散列連接。可以使用 BNL 和 NO_BNL 優化器提示,或通過設置 block_nested_loop=on 或 block_nested_loop=off 作爲優化器開關服務器系統變量設置的一部分,來控制是否採用散列連接。

#MySQL 8.0.18 支持在 optimizer_switch 中設置 hash_join 標誌,以及優化器提示 HASH_JOIN 和 NO_HASH_JOIN。在 MySQL 8.0.19 及更高版本中,這些提示都不再有效。

從 MySQL 8.0.18 開始,MySQL 對每個聯接都有等聯接條件的任何查詢都採用哈希聯接,其中沒有可應用於任何聯接條件的索引,如查詢:

SELECT *
    FROM t1
    JOIN t2
        ON t1.c1=t2.c1;

當有一個或多個索引可用於單表謂詞時,也可以使用哈希連接。

哈希連接通常比以前版本的 MySQL 中使用的塊嵌套循環算法(見塊嵌套循環連接算法)更快,因此在這種情況下使用哈希連接。從 MySQL 8.0.20 開始,對塊嵌套循環的支持被移除,服務器會在以前使用塊嵌套循環的地方使用哈希連接。

在剛纔的示例和本節的其他示例中,我們假設使用以下語句創建了三個表 t1、t2 和 t3:

CREATE TABLE t1 (c1 INT, c2 INT);
CREATE TABLE t2 (c1 INT, c2 INT);
CREATE TABLE t3 (c1 INT, c2 INT);

使用 EXPLAIN 可以看到哈希連接的使用情況,如下所示:

mysql> EXPLAIN
    -> SELECT * FROM t1
    ->     JOIN t2 ON t1.c1=t2.c1\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1
     filtered: 100.00
        Extra: NULL
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: t2
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 1
     filtered: 100.00
        Extra: Using where; Using join buffer (hash join)

只要每對錶中至少有一個連接條件是等連接,哈希連接也可用於涉及多重連接的查詢,如下面所示的查詢:

SELECT * FROM t1
    JOIN t2 ON (t1.c1 = t2.c1 AND t1.c2 < t2.c2)
    JOIN t3 ON (t2.c1 = t3.c1);

在如圖所示的使用內連接的情況下,任何非等連接的附加條件都會在連接執行後作爲篩選器應用。(對於外連接,如左連接、半連接和反連接,它們會作爲連接的一部分打印出來)。在 EXPLAIN 的輸出中可以看到這一點:

mysql> EXPLAIN FORMAT=TREE
    -> SELECT *
    ->     FROM t1
    ->     JOIN t2
    ->         ON (t1.c1 = t2.c1 AND t1.c2 < t2.c2)
    ->     JOIN t3
    ->         ON (t2.c1 = t3.c1)\G
*************************** 1. row ***************************
EXPLAIN: -> Inner hash join (t3.c1 = t1.c1)  (cost=1.05 rows=1)
    -> Table scan on t3  (cost=0.35 rows=1)
    -> Hash
        -> Filter: (t1.c2 < t2.c2)  (cost=0.70 rows=1)
            -> Inner hash join (t2.c1 = t1.c1)  (cost=0.70 rows=1)
                -> Table scan on t2  (cost=0.35 rows=1)
                -> Hash
                    -> Table scan on t1  (cost=0.35 rows=1)

從剛剛顯示的輸出結果中也可以看出,多重哈希連接可以(而且已經)用於具有多個等連接條件的連接。

在 MySQL 8.0.20 之前,如果任何一對連接的表沒有至少一個等連接條件,就不能使用散列連接,只能使用較慢的塊嵌套循環算法。在 MySQL 8.0.20 及更高版本中,散列連接可用於這種情況,如下所示:

mysql> EXPLAIN FORMAT=TREE
    -> SELECT * FROM t1
    ->     JOIN t2 ON (t1.c1 = t2.c1)
    ->     JOIN t3 ON (t2.c1 < t3.c1)\G
*************************** 1. row ***************************
EXPLAIN: -> Filter: (t1.c1 < t3.c1)  (cost=1.05 rows=1)
    -> Inner hash join (no condition)  (cost=1.05 rows=1)
        -> Table scan on t3  (cost=0.35 rows=1)
        -> Hash
            -> Inner hash join (t2.c1 = t1.c1)  (cost=0.70 rows=1)
                -> Table scan on t2  (cost=0.35 rows=1)
                -> Hash
                    -> Table scan on t1  (cost=0.35 rows=1)

哈希連接也適用於笛卡爾乘積,即未指定連接條件時,如圖所示:

mysql> EXPLAIN FORMAT=TREE
    -> SELECT *
    ->     FROM t1
    ->     JOIN t2
    ->     WHERE t1.c2 > 50\G
*************************** 1. row ***************************
EXPLAIN: -> Inner hash join  (cost=0.70 rows=1)
    -> Table scan on t2  (cost=0.35 rows=1)
    -> Hash
        -> Filter: (t1.c2 > 50)  (cost=0.35 rows=1)
            -> Table scan on t1  (cost=0.35 rows=1)

在 MySQL 8.0.20 及更高版本中,爲了使用散列連接,連接不再需要至少包含一個等連接條件。這意味着可以使用散列連接優化的查詢類型包括下面列表中的查詢(附示例):

內部非相等連接

mysql> EXPLAIN FORMAT=TREE SELECT * FROM t1 JOIN t2 ON t1.c1 < t2.c1\G
*************************** 1. row ***************************
EXPLAIN: -> Filter: (t1.c1 < t2.c1)  (cost=4.70 rows=12)
    -> Inner hash join (no condition)  (cost=4.70 rows=12)
        -> Table scan on t2  (cost=0.08 rows=6)
        -> Hash
            -> Table scan on t1  (cost=0.85 rows=6)

半連接

mysql> EXPLAIN FORMAT=TREE SELECT * FROM t1 
    ->     WHERE t1.c1 IN (SELECT t2.c2 FROM t2)\G
*************************** 1. row ***************************
EXPLAIN: -> Hash semijoin (t2.c2 = t1.c1)  (cost=0.70 rows=1)
    -> Table scan on t1  (cost=0.35 rows=1)
    -> Hash
        -> Table scan on t2  (cost=0.35 rows=1)

反連接

mysql> EXPLAIN FORMAT=TREE SELECT * FROM t2 
    ->     WHERE NOT EXISTS (SELECT * FROM t1 WHERE t1.c1 = t2.c1)\G
*************************** 1. row ***************************
EXPLAIN: -> Hash antijoin (t1.c1 = t2.c1)  (cost=0.70 rows=1)
    -> Table scan on t2  (cost=0.35 rows=1)
    -> Hash
        -> Table scan on t1  (cost=0.35 rows=1)

1 row in set, 1 warning (0.00 sec)

mysql> SHOW WARNINGS\G
*************************** 1. row ***************************
  Level: Note
   Code: 1276
Message: Field or reference 't3.t2.c1' of SELECT #2 was resolved in SELECT #1

左外連接

mysql> EXPLAIN FORMAT=TREE SELECT * FROM t1 LEFT JOIN t2 ON t1.c1 = t2.c1\G
*************************** 1. row ***************************
EXPLAIN: -> Left hash join (t2.c1 = t1.c1)  (cost=0.70 rows=1)
    -> Table scan on t1  (cost=0.35 rows=1)
    -> Hash
        -> Table scan on t2  (cost=0.35 rows=1)

右外連接(注意,MySQL 會將所有右外連接改寫爲左外連接):

mysql> EXPLAIN FORMAT=TREE SELECT * FROM t1 RIGHT JOIN t2 ON t1.c1 = t2.c1\G
*************************** 1. row ***************************
EXPLAIN: -> Left hash join (t1.c1 = t2.c1)  (cost=0.70 rows=1)
    -> Table scan on t2  (cost=0.35 rows=1)
    -> Hash
        -> Table scan on t1  (cost=0.35 rows=1)

默認情況下,MySQL 8.0.18 及更高版本會盡可能使用哈希連接。可以使用 BNL 和 NO_BNL 優化器提示之一來控制是否使用哈希連接。

(MySQL 8.0.18 支持 hash_join=on 或 hash_join=off,作爲 optimizer_switch 服務器系統變量以及優化器提示 HASH_JOIN 或 NO_HASH_JOIN 設置的一部分。在 MySQL 8.0.19 及更高版本中,這些提示不再有任何作用)。

可以使用 join_buffer_size 系統變量來控制哈希連接的內存使用量;哈希連接使用的內存不能超過此值。當哈希連接所需的內存超過可用容量時,MySQL 會使用磁盤上的文件來處理。如果出現這種情況,你應該注意,如果哈希連接無法容納內存,並且創建的文件超過 open_files_limit 的設置,那麼連接可能不會成功。爲避免此類問題,請進行以下更改:

  • 增大 join_buffer_size,以免散列連接溢出到磁盤。
  • 增加 open_files_limit.

從 MySQL 8.0.18 開始,哈希連接的連接緩衝區以增量方式分配;因此,您可以將 join_buffer_size 設置得更高,而不會讓小查詢分配大量 RAM,但外連接會分配整個緩衝區。在 MySQL 8.0.20 及更高版本中,哈希連接也用於外部連接(包括反連接和半連接),因此這不再是一個問題。

8.2.1.5 引擎條件下推優化

這一優化提高了非索引列與常數之間直接比較的效率。在這種情況下,條件會被 "下推 "到存儲引擎進行評估。該優化僅適用於 NDB 存儲引擎。

對於 NDB 羣集,這種優化可以消除在羣集的數據節點和發出查詢的 MySQL 服務器之間通過網絡發送非匹配行的需要,與可以但未使用條件下推的情況相比,使用這種優化的查詢速度可提高 5 到 10 倍。

假設 NDB 羣集表定義如下:

CREATE TABLE t1 (
    a INT,
    b INT,
    KEY(a)
) ENGINE=NDB;

引擎條件下推可用於查詢,如圖所示,其中包括非索引列和常數之間的比較:

SELECT a, b FROM t1 WHERE b = 10;

從 EXPLAIN 的輸出中可以看到引擎狀態下推的使用:

mysql> EXPLAIN SELECT a, b FROM t1 WHERE b = 10\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 10
        Extra: Using where with pushed condition

但是,引擎條件下推不能與以下查詢一起使用:

SELECT a,b FROM t1 WHERE a = 10;

由於 a 列上存在一個索引,因此這裏不適用引擎條件下推(索引訪問方法效率更高,因此會優先選擇條件下推)。

當使用 > 或 < 操作符將索引列與常量進行比較時,也可以使用引擎條件下推:

mysql> EXPLAIN SELECT a, b FROM t1 WHERE a < 2\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t1
         type: range
possible_keys: a
          key: a
      key_len: 5
          ref: NULL
         rows: 2
        Extra: Using where with pushed condition

其他支持的引擎狀態下推比較包括以下內容:

  • column [NOT] LIKE pattern
  • column IS [NOT] NULL
  • column IN (value_list)
  • column BETWEEN constant1 AND constant2

在前面列出的所有情況中,都可以將條件轉換爲列和常量之間的一個或多個直接比較的形式。

 

引擎條件下推默認爲啓用。要在服務器啓動時禁用,請將 optimizer_switch 系統變量的 engine_condition_pushdown 標誌設置爲關閉。例如,在 my.cnf 文件中使用以下幾行:

[mysqld]
optimizer_switch=engine_condition_pushdown=off

在運行時,像這樣禁用條件下推:

SET optimizer_switch='engine_condition_pushdown=off';

僅 NDB 存儲引擎支持引擎條件推送。

在 NDB 8.0.18 之前,列只能與常量或只評估常量值的表達式進行比較。在 NDB 8.0.18 及更高版本中,只要列的類型完全相同,包括相同的符號性、長度、字符集、精度和比例(如果適用),列之間就可以相互比較。

用於比較的列不能是任何 BLOB 或 TEXT 類型。這種排除也適用於 JSON、BIT 和 ENUM 列。

要與列進行比較的字符串值必須使用與列相同的校對方式。

不直接支持連接;涉及多個表的條件儘可能單獨推送。請使用擴展的 EXPLAIN 輸出來確定哪些條件被實際推送

以前,引擎條件下推僅限於引用條件被下推到的同一表中的列值。從 NDB 8.0.16 開始,查詢計劃中較早的表中的列值也可以從推送條件中引用。這就減少了 SQL 節點在連接處理過程中必須處理的行數。過濾也可以在 LDM 線程中並行執行,而不是在單個 mysqld 進程中執行。這有可能顯著提高查詢性能。

 

從 NDB 8.0.20 開始,如果在同一連接巢中使用的任何表上,或在其所依賴的連接巢上的任何表上沒有不可推送的條件,那麼使用掃描的外部連接就可以被推送。如果使用的優化策略是 firstMatch,那麼半連接也是如此。

在以下兩種情況下,不能將連接算法與引用以前表中的列結合起來:

當引用的任何先前表都在連接緩衝區中時。在這種情況下,從掃描過濾表中檢索的每一行都要與緩衝區中的每一行進行匹配。這意味着在生成掃描篩選器時,無法從單一特定行中獲取列值。

當列來自推入連接中的子操作時。這是因爲在生成掃描篩選器時,尚未檢索從連接中的祖先操作引用的行。

 

從 NDB 8.0.27 開始,只要符合前面列出的要求,就可以向下推送連接中來自祖表的列。下面是使用之前創建的表 t1 進行此類查詢的示例:

 

 


8.2.1.6 索引條件下推優化
8.2.1.7 嵌套循環連接算法
8.2.1.8 嵌套連接優化
8.2.1.9 外連接優化
8.2.1.10 外連接簡化
8.2.1.11 多範圍讀取優化
8.2.1.12 塊嵌套循環和分批密鑰訪問連接
8.2.1.13 條件過濾
8.2.1.14 恆定摺疊優化
8.2.1.15 IS NULL 優化
8.2.1.16 ORDER BY 優化
8.2.1.17 GROUP BY 優化
8.2.1.18 DISTINCT 優化
8.2.1.19 LIMIT 查詢優化
8.2.1.20 函數調用優化
8.2.1.21 窗口函數優化
8.2.1.22 行構造表達式優化
8.2.1.23 避免全表掃描

二、 優化子查詢、派生表、視圖引用和公用表 表達 式

三、 優化INFORMATION_SCHEMA查詢

四、 優化性能架構查詢

五、 優化數據變更語句

六、 優化數據庫權限

七、 其他優化技巧

column [NOT] LIKE pattern
column IS [NOT] NULL
column IN (value_list)
column BETWEEN constant1 AND constant2
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章