1. 程式人生 > >MySQL查詢優化之範圍優化

MySQL查詢優化之範圍優化

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

譯文:

8.2.1.2 範圍優化

範圍訪問方法使用單個索引檢索包含在一個或多個索引值區間內的錶行子集。它可以用於單列或複合索引。下面各部分描述的是優化器在不同條件下使用rang訪問方法的情況。

用於單列索引的範圍訪問方法

對於單列索引,索引值區間可以方便地用where子句中的相應條件表示,表示為範圍條件,而不是“區間”。

單列索引的範圍條件的定義如下所示:

    1)對於B-tree和雜湊索引,當使用=,<=>,in(),is null 或is not null這些操作符時,索引鍵與常量值的比較是一個範圍條件。

    2)此外,對於B-tree索引,當使用>、<、>=、<=、between、!=或<>這些操作符或者引數是常量字串且不以萬用字元開頭的like關鍵字時,索引鍵與常量的比較是一個範圍條件。

    3)對於所有索引型別,多範圍條件與or或and形成一個範圍條件。

前面所描述的常量值指的是如下所示情況:

    1)查詢字串中的常量

    2)同一連線裡常量表或系統表中的一列

    3)不關聯子查詢的結果

    4)完全由上述型別的子表示式組成的任何表示式

下面是一些在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子句中為每個可能的索引提取範圍條件。在提取過程中,刪除不能用於構造範圍條件的條件和產生空範圍的條件,合併產生重疊範圍的條件。

考慮下面的語句,其中key2是一個索引列,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)Collapse conditions總是為真或假:

(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不支援為空間索引的範圍訪問方法合併多個範圍。為了克服這個限制,可以使用具有相同select語句的union,但是需要將每個空間謂詞放在不同的select中。

用於複合索引的範圍訪問方法

複合索引的範圍條件是單列索引範圍條件的擴充套件。複合索引上的範圍條件限制索引行位於一個或多個索引鍵元組區間。索引鍵元組區間是在一組索引鍵元組上定義的,使用索引中的順序。

例如,考慮定義為key1(key_part1key_part2key_part3)的複合索引,下面的索引鍵元組就是按索引鍵順序排列的:

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' 這個條件沒有定義一個區間,因此也就無法用於範圍訪問方法。

下文的描述詳細表明瞭範圍訪問方法如何作用於複合索引:

    1)對於雜湊索引來說,可以使用每個包含相同值的區間。這意味著只有在以下條件下才能產生區間:

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

這裡的const1const2, … 都是常量,,cmp 是如=,<=>,或is null這樣的比較操作符,條件覆蓋了所有的索引 (也就是說,有N個條件,每個條件都對應著複合索引N-part index中的一個 ) 例如,下面是一個對應於三列複合雜湊索引的範圍條件:

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

關於常量的定義,可以參考前文用於單列索引的範圍訪問方法。

2)對於一個B-tree索引來說,區間可以用於與and結合的條件,其中每個條件用=,<=>,is null,>,<,>=,<=,!=,<>,between或者like '匹配模式'(匹配模式中不以萬用字元開頭)這樣的操作符把每個條件與一個常量值進行比較。只要能決定單列索引鍵元組包含所有與條件匹配的行,單一區間就可以使用(或者是使用了<>/!=操作符的兩個區間)。

只要是使用了=,<=>,或者is null操作符,MySQL優化器就嘗試使用額外的索引鍵來確定區間。如果操作符是>,<,>=,<=,!=,<>,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),這並不符合原始條件。

    3)如果覆蓋區間內包含行集的條件與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)

在這個例子中,第一行的區間使用複合索引中的單列作為左邊邊界,使用複合索引中的兩列作為右邊邊界。第二行的區間只使用了複合索引中的一列。expalin輸出中的key_len列表示所使用的索引鍵字首的最大長度。

在一些情況下,key_len列表明瞭有一個索引鍵被使用,但這可能不是你期望的。假設key_part1和key_part2可以為空。然後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等於幾個值中的任意一個,則每個表示式都為真。這些比較都是等值範圍比較(其中,範圍是單一值)。優化器評估讀取符合條件的行進行等值範圍比較的成本如下:

    1)如果col_name上有唯一索引,則每個範圍的行估計值為1,因為最多一行可以有給定的值。

    2)否則,如果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。

要更新表索引統計資訊以獲得最佳估計,可以使用使用分析表。

在索引潛水可能被使用的條件下,他們跳過滿足所有這些條件的查詢:

    1)存在強制使用單列索引的索引暗示。 其思想是,如果索引使用是強制的,那麼執行索引潛水的額外開銷不會帶來任何好處。

    2)索引是非唯一索引並且不是全文搜尋索引

    3)不存在子查詢

    4)不存在distinct,group by或order by子句

這些潛水跳躍式條件僅適用於單表查詢。對於多表查詢(joins),索引潛水不會被跳過。

行構造器表示式的範圍優化

優化器能夠將範圍掃描訪問方法應用到如下所示查詢:

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' );

假如優化器要使用範圍掃描,查詢必須滿足如下條件:

    1)只有in()謂詞被用到,而不是not in()

    2)在in()謂詞的左側,行構造器只包含列引用

    3)在in()謂詞的右側,行建構函式只包含執行時常量,這些常量是在執行期間繫結到常量的文字或本地列引用

    4)在in()謂詞的右側,有多個行構造器

有關優化器和行構造函器的更多資訊,可以參考:Section 8.2.1.19, “Row Constructor Expression Optimization”

範圍優化的記憶體使用限制

為了控制範圍優化器可用的記憶體,可以使用系統變數range_optimizer_max_mem_size:

    1)0值意味著沒有限制

    2)如果值大於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.

    3)對於update和delete語句,如果優化器退回到全表掃描,並且啟用了系統變數sql_safe_updates,則會發生錯誤而不是警告。因為,實際上,沒有索引鍵用於確定要修改哪些行。有關更多資訊,可以參考Section 4.5.1.6.4, “Using Safe-Updates Mode (--safe-updates)”

對於超出可用範圍優化記憶體且優化器退回到不太優化的計劃的單個查詢,增加變數range_optimizer_max_mem_size的值可能會提高效能。

要估計處理範圍表示式所需的記憶體量,請使用以下準則:

    1)對於如下所示簡單查詢,其中有一個範圍訪問方法的候選鍵,每個與or結合的謂詞大約使用230個位元組:

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

    2)類似的,對於如下所示查詢,每個與and結合的謂詞大約使用125個位元組:

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

    3)至於使用in()謂詞的查詢:

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

    in()列表中的每個文字值都算作與or組合的謂詞。如果有兩個in()列表,則與or結合的謂詞的數量是每個列表中文字值的數量的乘積。因此,前文示例中與or結合的謂詞的數量是M × N。

在5.7.11版本之前,與or結合的謂詞使用的位元組數量更高,大約是700個位元組。

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