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

8.2.1.13 條件過濾 

在連接處理中,前綴行是從連接中的一個表傳遞到下一個表的行。一般來說,優化程序會嘗試將前綴數較少的表放在連接順序的前面,以防止行組合數迅速增加。如果優化器能使用從一個表中選擇並傳遞到下一個表的行的條件信息,就能更準確地計算行估計值並選擇最佳執行計劃。

如果不使用條件過濾,那麼表的前綴行數將基於 WHERE 子句根據優化器選擇的訪問方法估計行數。條件過濾使優化器能夠在訪問方法未考慮到的WHERE子句中使用其他相關條件,從而改進其前綴行行數估計值。例如,即使有一種基於索引的訪問方法可用於在連接中從當前表中選擇行,但在 WHERE 子句中也可能存在表的其他條件,可以過濾(進一步限制)傳遞到下一個表的合格行的估計值。

只有在以下情況下,條件纔會對過濾估計有所幫助:

  • 它指的是當前表格。
  • 它取決於一個常量值或來自連接序列中早期表的值。
  • 訪問方法尚未將其考慮在內。

在 EXPLAIN 輸出中,行列 表示所選訪問方法的行估計值,過濾列反映條件過濾的效果。最大值爲 100,表示沒有過濾行。從 100 開始遞減的值表示過濾量在增加。

前綴行數(在連接中從當前表傳遞到下一個表的估計行數)是行數與過濾值的乘積。也就是說,前綴行數是估計的行數,再減去估計的過濾效果。例如,如果行數爲 1000,過濾值爲 20%,則條件過濾會將估計行數 1000 減少爲前綴行數 1000 × 20% = 1000 × 0.2 = 200。

請看下面的查詢:

SELECT *
  FROM employee JOIN department ON employee.dept_no = department.dept_no
  WHERE employee.first_name = 'John'
  AND employee.hire_date BETWEEN '2018-01-01' AND '2018-06-01';

假設該數據集具有以下特徵:

  • 僱員表有 1024 行。
  • 部門表有 12 行。
  • 兩個表都有一個關於 dept_no 的索引。
  • 僱員表在 first_name 上有一個索引。
  • 有 8 條記錄滿足 employee.first_name 上的條件:
employee.first_name = 'John'
  • 有 150 條記錄滿足 employee.hire_date 的這一條件:

 

employee.hire_date BETWEEN '2018-01-01' AND '2018-06-01'
  • 有 1 行同時滿足這兩個條件:
  • employee.first_name = 'John'
    AND employee.hire_date BETWEEN '2018-01-01' AND '2018-06-01'

     

如果不進行條件過濾,EXPLAIN 會產生這樣的輸出結果:

+----+------------+--------+------------------+---------+---------+------+----------+
| id | table      | type   | possible_keys    | key     | ref     | rows | filtered |
+----+------------+--------+------------------+---------+---------+------+----------+
| 1  | employee   | ref    | name,h_date,dept | name    | const   | 8    | 100.00   |
| 1  | department | eq_ref | PRIMARY          | PRIMARY | dept_no | 1    | 100.00   |
+----+------------+--------+------------------+---------+---------+------+----------+

就僱員而言,名稱索引的訪問方法會獲取與名稱 "John "匹配的 8 條記錄。沒有進行過濾(過濾率爲 100%),因此所有記錄都是下一個表的前綴行:前綴行計數爲行 × 過濾 = 8 × 100% = 8。

通過條件過濾,優化器會額外考慮訪問方法未考慮的 WHERE 子句中的條件。在本例中,優化器使用啓發式方法估算了 employee.hire_date 的 BETWEEN 條件的過濾效果爲 16.31%。因此,EXPLAIN 的輸出結果如下:

+----+------------+--------+------------------+---------+---------+------+----------+
| id | table      | type   | possible_keys    | key     | ref     | rows | filtered |
+----+------------+--------+------------------+---------+---------+------+----------+
| 1  | employee   | ref    | name,h_date,dept | name    | const   | 8    | 16.31    |
| 1  | department | eq_ref | PRIMARY          | PRIMARY | dept_no | 1    | 100.00   |
+----+------------+--------+------------------+---------+---------+------+----------+

現在,前綴行數爲行 × 篩選 = 8 × 16.31% = 1.3,更接近實際數據集。

通常情況下,優化器不會計算最後一個連接表的條件過濾效果(前綴行數減少),因爲沒有下一個表可以傳遞行。但 EXPLAIN 的情況例外:爲了提供更多信息,會計算所有連接表(包括最後一個表)的過濾效果。

要控制優化器是否考慮額外的過濾條件,可以使用 optimizer_switch 系統變量的 condition_fanout_filter 標誌(參見第 8.9.2 節 "可切換優化")。默認情況下啓用該標誌,但也可以禁用該標誌來抑制條件過濾(例如,如果發現某個查詢在不使用條件過濾的情況下性能更好)。

如果優化器高估了條件過濾的效果,性能可能會比不使用條件過濾時更差。在這種情況下,這些技術可能會有所幫助:

  • 如果未對某列進行索引,則對其進行索引,這樣優化器就能獲得一些關於列值分佈的信息,從而改進對行的估計。
  • 同樣,如果沒有列直方圖信息,可以生成直方圖(參見第 8.9.6 節 "優化器統計")。
  • 更改連接順序。實現這一點的方法包括連接順序優化提示(參見第 8.9.3 節 "優化提示")、緊跟 SELECT 的 STRAIGHT_JOIN 以及 STRAIGHT_JOIN 連接操作符。
  • 禁用會話的條件篩選:
  • SET optimizer_switch = 'condition_fanout_filter=off';

    或者,對於給定查詢,使用優化器提示:

  • SELECT /*+ SET_VAR(optimizer_switch = 'condition_fanout_filter=off') */ ...

     

 

8.2.1.14 常數摺疊優化

常數摺疊(Constant folding)以及常數傳播(constant propagation)都是編譯器最佳化技術,他們被使用在現代的編譯器中。進階的常數傳播形式,或稱之爲稀疏有條件的常量傳播(sparse conditional constant propagation),可以更精確地傳播常數及無縫的移除無用的程式碼。(提前把值算出來)

在常量和列值之間進行比較時,如果常量值超出範圍或相對於列類型而言類型錯誤,現在會在查詢優化過程中逐行處理,而不是在執行過程中處理。以這種方式處理的比較有 >、>=、<、<=、<>/!=、= 和 <=>。

請看下面語句創建的表格:

CREATE TABLE t (c TINYINT UNSIGNED NOT NULL);

在查詢 SELECT * FROM t WHERE c < 256 中的 WHERE 條件包含整數常數 256,這超出了 TINYINT UNSIGNED 列的範圍。以前的處理方法是將兩個操作數都視爲較大的類型,但現在,由於 c 的任何允許值都小於常數,WHERE 表達式可以摺疊爲 WHERE 1,這樣查詢就改寫爲 SELECT * FROM t WHERE 1。

這樣,優化器就可以完全刪除 WHERE 表達式。如果列 c 是可空的(即只定義爲 TINYINT UNSIGNED),查詢可以這樣重寫:

SELECT * FROM t WHERE ti IS NOT NULL

與支持的 MySQL 列類型相比,常量的摺疊方法如下:

Integer column type 整數類型與以下類型的常量進行比較,如此處所述:

Integer value.如果常量超出列類型的範圍,比較結果將摺疊爲 1 或 IS NOT NULL,如前所述。如果常量是範圍邊界,則比較結果將摺疊爲 =:

mysql> EXPLAIN SELECT * FROM t WHERE c >= 255;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: t
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 5
     filtered: 20.00
        Extra: Using where
1 row in set, 1 warning (0.00 sec)

mysql> SHOW WARNINGS;
*************************** 1. row ***************************
  Level: Note
   Code: 1003
Message: /* select#1 */ select `test`.`t`.`ti` AS `ti` from `test`.`t` where (`test`.`t`.`ti` = 255)
1 row in set (0.00 sec)

Floating- or fixed-point value.

如果常數是十進制類型(如 DECIMAL、REAL、DOUBLE 或 FLOAT)之一,且小數部分不爲零,則不可能相等;因此要進行相應的摺疊。對於其他比較,根據符號向上或向下舍入爲整數值,然後執行範圍檢查,並按照前面所述的整數-整數比較進行處理。

如果一個 REAL 值太小,無法用十進制表示,則會根據符號四捨五入爲 .01 或 -.01,然後按十進制處理。

String types. 

嘗試將字符串值解釋爲整數類型,然後在整數值之間進行比較。如果失敗,則嘗試將值作爲 REAL 處理。

 DECIMAL or REAL column. 十進制類型與以下類型的常量進行比較,如此處所述:

Integer value. 對列值的整數部分進行範圍檢查。如果沒有摺疊結果,則將常數轉換爲小數位數,小數位數與列值相同,然後將其作爲小數位數進行檢查(見下一步)。

DECIMAL or REAL value. 檢查是否有溢出(即常數整數部分的位數是否超過列的十進制類型所允許的位數)。如果是,則摺疊。

String value.  如果數值可以解釋爲整數類型,則按整數類型處理。否則,儘量按 REAL 類型處理。

FLOAT or DOUBLE column. 

FLOAT(m,n) 或 DOUBLE(m,n) 值與常量相比的處理方法如下:

如果數值超出列的範圍,則摺疊。

如果數值超過 n 個小數,則截斷,在摺疊過程中進行補償。對於 = 和 <> 比較,如前所述,摺疊爲 TRUE、FALSE 或 IS [NOT] NULL;對於其他操作符,調整操作符。

如果數值的整數位數超過 m,則摺疊。

限制  以下情況不能使用此優化:

使用 BETWEEN 或 IN 進行比較。

使用 BIT 列或使用日期或時間類型的列。

在準備語句的準備階段,儘管可以在優化階段實際執行準備語句時使用。這是因爲在語句準備期間,常量的值尚未確定。

8.2.1.15 IS NULL 優化

MySQL 可以對 col_name IS NULL 執行與 col_name = constant_value 相同的優化。例如,MySQL 可以使用索引和範圍來搜索 IS NULL 中的 NULL。

例如

SELECT * FROM tbl_name WHERE key_col IS NULL;

SELECT * FROM tbl_name WHERE key_col <=> NULL;

SELECT * FROM tbl_name
  WHERE key_col=const1 OR key_col=const2 OR key_col IS NULL;

如果 WHERE 子句包含一個 col_name IS NULL 條件,而該列已聲明爲 NOT NULL,那麼該表達式將被優化掉。如果列無論如何都可能產生 NULL(例如,如果列來自 LEFT JOIN 右側的表),則不會進行這種優化。

MySQL 還可以優化 col_name = expr OR col_name IS NULL 組合,這種形式在解析子查詢中很常見。使用這種優化時,EXPLAIN 會顯示 ref_or_null。

這種優化可以處理任何鍵部分的一個 IS NULL。

假設表 t2 的 a 列和 b 列上有索引,優化後查詢的一些示例:

 

 

 

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 避免全表掃描

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章