在聯結處理中,前綴列是從聯結中的一個表格傳遞到下一個表格的列。一般來說,最佳化器會嘗試將前綴計數較低的表格放在聯結順序的早期,以防止列組合的數量快速增加。最佳化器可以利用從一個表格選取並傳遞到下一個表格的列條件資訊的程度越高,它就能更精確地計算列估計值並選擇最佳的執行計畫。
如果沒有條件過濾,表格的前綴列計數會根據最佳化器選擇的任何存取方法,以 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
子句中未被存取方法考量的條件。在此情況下,最佳化工具會使用啟發式方法來估計 BETWEEN
條件在 employee.hire_date
上的篩選效果為 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') */ ...