MySQL查詢優化之函式呼叫的優化
原文地址:https://dev.mysql.com/doc/refman/5.7/en/function-optimization.html
譯文:
8.2.1.18 函式呼叫優化
MySQL函式在內部被標記為確定性或非確定性。如果傳入固定值作為引數,函式對於不同的呼叫可以返回不同的結果,那麼函式是非確定性的。非確定性的函式示例有:RAND()、UUID()。
如果一個函式被標記為非確定性的,where子句中對該函式的引用會作用於每個行(當僅從一個表中查詢資料)或者作用於行的組合(當從多表的連線查詢資料)。
MySQL還根據引數的型別決定什麼時候計算函式,引數是表列還是常量。對於一個採用表列作為引數的確定性函式,當列值改變時,該函式一定會被計算。
非確定性的函式可能會影響查詢的效能。例如,一些優化可能不使用,或者需要更多的鎖。下面的討論使用RAND(),但也適用於其他不確定函式。
假設表t有這樣的定義:
-
CREATE TABLE t (id INT NOT NULL PRIMARY KEY, col_a VARCHAR(100));
考慮下面的兩個查詢:
-
SELECT * FROM t WHERE id = POW(1,2); SELECT * FROM t WHERE id = FLOOR(1 + RAND() * 49);
從與主鍵的相等性比較可以看出,這兩個查詢似乎都使用了主鍵查詢,但只有第一個查詢使用了主鍵查詢:
1)第一個查詢總是生成最多一行,因為引數為常數的POW()函式值是一個常數值,用於索引查詢。
2)第二個查詢包含了一個使用非確定性函式RAND()的表示式,該表示式不是一個常數,但實際上對於表t中的每一行,它都會有一個新值與之對應。因此,查詢讀取表中的每一行,計算每一行的謂詞,並輸出主鍵與隨機值(由表示式產生)匹配的所有行。根據id列值和RAND()序列中的值,這可能是0、1或多個行。
不確定性的影響並不侷限於SELECT語句。下面這個UPDATE語句使用一個非確定性函式來選擇要修改的行:
-
UPDATE t SET col_a = some_expr WHERE id = FLOOR(1 + RAND() * 49);
可能的目的是最多隻更新主鍵與表示式匹配的一行。但是,根據id列值和RAND()序列中的值,它可能更新0、1或多個行。
剛才描述的行為對效能和複製有影響:
1)由於不確定性函式不產生常量值,優化器不能使用其他函式可能適用的策略,例如索引查詢。結果可能是表掃描;
2)InnoDB可能會升級為一個範圍鍵鎖,而不是為一個匹配的行使用單個行鎖;
3)不確定執行的更新對於複製是不安全的。
這些困難源於這樣一個事實,即RAND()函式對錶的每一行求值一次。為了避免多重函式計算,可以使用以下技術之一:
1)將包含非確定性函式的表示式移動到單獨的語句中,將值儲存在變數中。在最初的語句中,將表示式替換為一個引用變數,優化器可以將其視為一個常數值:
-
SET @keyval = FLOOR(1 + RAND() * 49); UPDATE t SET col_a = some_expr WHERE id = @keyval;
2)將隨機值賦給派生表中的變數。這種技術使變數在WHERE子句的比較中使用之前被賦值一次:
-
SET optimizer_switch = 'derived_merge=off'; UPDATE t, (SELECT @keyval := FLOOR(1 + RAND() * 49)) AS dt SET col_a = some_expr WHERE id = @keyval;
如前所述,WHERE子句中的不確定性表示式可能會阻止優化並導致表掃描。但是,如果其他表示式是確定的,則可以部分優化WHERE子句。例如:
-
SELECT * FROM t WHERE partial_key=5 AND some_column=RAND();
如果優化器可以使用partial_key來減少所選擇的行集,那麼RAND()的執行次數就會減少,這就減小了不確定性對優化的影響。
PS:由於水平有限,譯文中難免會存在謬誤,歡迎批評指正。