在聯結處理中,前置資料列是指從聯結中的一個資料表傳遞到下一個資料表的資料列。一般來說,最佳化工具會嘗試將前置計數較低的資料表放在聯結順序的早期,以避免資料列組合的數量快速增加。最佳化工具能夠使用關於從一個資料表選取並傳遞到下一個資料表的資料列條件的資訊的程度越高,它就能夠越準確地計算資料列估計值並選擇最佳執行計畫。
如果沒有條件篩選,資料表的前置資料列計數會根據最佳化工具選擇的存取方法,基於 WHERE
子句所選取的估計資料列數。條件篩選讓最佳化工具能夠使用 WHERE
子句中其他與存取方法無關的相關條件,進而改善其前置資料列計數估計值。例如,即使可能存在基於索引的存取方法,可用於從聯結中的目前資料表選取資料列,WHERE
子句中可能也存在該資料表的其他條件,可用於篩選(進一步限制)傳遞到下一個資料表的合格資料列估計值。
只有在下列情況下,條件才會對篩選估計值有所貢獻:
它參照目前的資料表。
它取決於聯結序列中較早資料表的常數值或值。
該存取方法尚未將其納入考量。
在 EXPLAIN
輸出中,rows
資料行表示所選取存取方法的資料列估計值,而 filtered
資料行則反映條件篩選的效果。filtered
值以百分比表示。最大值為 100,表示沒有發生資料列篩選。從 100 遞減的值表示篩選量逐漸增加。
前置資料列計數(估計從聯結中的目前資料表傳遞到下一個資料表的資料列數)是 rows
與 filtered
值的乘積。也就是說,前置資料列計數是估計資料列計數,並減去估計的篩選效果。例如,如果 rows
為 1000 且 filtered
為 20%,則條件篩選會將估計的 1000 資料列計數減少為 1000 × 20% = 1000 × .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';
假設資料集具有這些特性
employee
資料表有 1024 個資料列。department
資料表有 12 個資料列。兩個資料表在
dept_no
上都有索引。employee
資料表在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 |
+----+------------+--------+------------------+---------+---------+------+----------+
針對 employee
,name
索引的存取方法會選取符合名稱為 'John'
的 8 列資料。由於沒有進行篩選(filtered
為 100%),因此所有列都是下一個資料表的字首列:字首列計數為 rows
× filtered
= 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 |
+----+------------+--------+------------------+---------+---------+------+----------+
現在,字首列計數為 rows
× filtered
= 8 × 16.31% = 1.3,這更能反映實際資料集。
通常,最佳化工具不會計算最後一個聯結資料表的條件篩選效果(字首列計數減少),因為沒有下一個資料表可以傳遞列。但對於 EXPLAIN
,則會例外處理:為了提供更多資訊,會計算所有聯結資料表(包括最後一個)的篩選效果。
若要控制最佳化工具是否考慮其他篩選條件,請使用 optimizer_switch
系統變數的 condition_fanout_filter
旗標(請參閱 第 10.9.2 節「可切換的最佳化」)。此旗標預設為啟用,但可以停用以抑制條件篩選(例如,如果發現特定查詢在不使用條件篩選的情況下效能更好)。
如果最佳化工具高估了條件篩選的效果,則效能可能會比不使用條件篩選時更差。在這種情況下,這些技巧可能會有所幫助:
如果某個欄位沒有索引,請為其建立索引,以便最佳化工具可以獲得一些關於欄位值分佈的資訊,並可以改善其列估算。
同樣地,如果沒有可用的欄位直方圖資訊,請產生直方圖(請參閱 第 10.9.6 節「最佳化工具統計資料」)。
變更聯結順序。達成此目的的方法包括聯結順序最佳化工具提示(請參閱 第 10.9.3 節「最佳化工具提示」)、緊接在
SELECT
後面的STRAIGHT_JOIN
,以及STRAIGHT_JOIN
聯結運算子。停用此工作階段的條件篩選。
SET optimizer_switch = 'condition_fanout_filter=off';
或者,針對給定的查詢使用最佳化工具提示。
SELECT /*+ SET_VAR(optimizer_switch = 'condition_fanout_filter=off') */ ...