1. 程式人生 > >MySQL查詢優化之條件過濾

MySQL查詢優化之條件過濾

原文地址:https://dev.mysql.com/doc/refman/5.7/en/condition-filtering.html

譯文:

8.2.1.12 條件過濾

在連線處理中,字首行是連線中從一個表傳遞到另一個表的行。通常,優化器試圖將字首計數較低的表放在連線的早期,以防止行組合的數量快速增長。優化器可以使用關於從一個表中選擇並傳遞到下一個表中的行條件的資訊,因此它可以更準確地計算行估計並選擇最佳執行計劃。

如果沒有條件過濾,表的字首行計數將基於where子句根據優化器選擇的任何訪問方法查詢出的估計行數。條件過濾使優化器能夠在where子句中使用訪問方法沒有考慮到的其他相關條件,從而改進其字首行數估計。例如,儘管可能會有一個基於索引的訪問方法可以用來從連線中的當前表查詢行,也可能在where子句中有表的附加條件可以用來過濾(進一步限制)傳遞到下一個表的符合條件的行的估計值。

只有滿足下列任意一種情況的條件才有利於過濾估計:

    1)它引用當前表;

    2)它依賴於一個常量值或連線序列中較早表中的值;

    3)訪問方法還沒有考慮到這一條件。

在explain的輸出結果中,rows列表示所選訪問方法的行估計,filtered列反應條件過濾的效果。filtered列的值用百分數表示。最大值是100,意味著沒有行被過濾掉。從100逐漸減小的值表示被過濾掉的行數在增加。

字首行計數(在連線中從當前表傳遞到下一個表的行數)是rows列值和filtered列值的乘積。也就是說,字首行計數是估算的行計數,因條件過濾過濾而減小。例如,如果rows的值是1000,filtered的值是20%,條件過濾把行估計由1000減少到字首行計數1000×20%=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';

假設資料集有如下所示特性:

    1)employee表有1024行;

    2)department表有12行;

    3)每個表在dept_no列上都建有索引;

    4)employee表在first_name列上建有索引;

    5)滿足employee.first_name條件的資料有8行;

employee.first_name = 'John'

    6)滿足employee.hire_date條件的資料有150行:

employee.hire_date BETWEEN '2018-01-01' AND '2018-06-01'

滿足所有的條件的資料只有一行:

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   |
+----+------------+--------+------------------+---------+---------+------+----------+

對於employee表來說,name索引上的訪問方法檢索出了與名字'Jhon'匹配的8行資料。沒有過濾(filtered值是100%),所以所有行都是下一個表的字首行:字首行的計數rows × filtered = 8 × 100% = 8。

使用條件過濾時,優化器優化器還會考慮訪問方法沒有考慮的來自where子句的條件。在這種情況下,優化器使用啟發法估計出employee.hire_date上的between條件的過濾效果為16.31%。因此,expalin會產生如下輸出:

+----+------------+--------+------------------+---------+---------+------+----------+
| 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   |
+----+------------+--------+------------------+---------+---------+------+----------+

現在字首行數為 rows × filtered = 8 × 16.31% = 1.3,更緊密地反映了實際的資料集。通常,優化器不會為最後一個連線的表計算條件過濾效果(減少字首行數),因為沒有可以向其傳遞行的下一個表。explain出現了一個例外:為了提供更多資訊,對所有連線的表(包括最後一個表)計算過濾效果。

要控制優化器是否考慮其他過濾條件,可以使用系統變數optimizer_switch的condition_fanout_filter標誌(參考Section 8.9.3, “Switchable Optimizations”)。這個標誌在預設情況下是啟用的,但是可以禁用它來抑制條件過濾(例如,如果發現某個特定查詢在沒有條件過濾的情況下可以獲得更好的效能)。

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

    1)如果一個列沒有被索引,那麼對它進行索引,這樣優化器就可以獲得關於列值分佈的一些資訊,並可以改進它的行估計。

    2)更改連線順序。實現這一點的方法包括連線順序優化器提示(參考Section 8.9.2, “Optimizer Hints”),STRAIGHT_JOIN緊跟著SELECT和STRAIGHT_JOIN連線操作符。

    3)禁用會話的條件過濾:

SET optimizer_switch = 'condition_fanout_filter=off';

PS:由於水平有限,譯文中難免存在謬誤,歡迎批評指正。