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);
由於與主鍵的相等比較,這兩個查詢似乎都使用了主鍵查詢,但這只適用於第一個查詢
非決定性的影響不僅限於 SELECT
陳述式。這個 UPDATE
陳述式使用非決定性函數來選取要修改的列
UPDATE t SET col_a = some_expr WHERE id = FLOOR(1 + RAND() * 49);
據推測,其目的是更新最多一個主鍵與表達式匹配的列。但是,它可能會更新零列、一列或多列,取決於 id
列的值和 RAND()
序列中的值。
剛才描述的行為對效能和複寫有影響
由於非決定性函數不會產生常數值,因此最佳化器無法使用其他可能適用的策略,例如索引查詢。結果可能是表格掃描。
InnoDB
可能會升級為範圍鍵鎖,而不是為一個匹配列取得單列鎖。未以決定性方式執行的更新對於複寫是不安全的。
這些困難源於事實,即 RAND()
函數會針對表格的每一列評估一次。為了避免多次函數評估,請使用以下其中一種技術
將包含非決定性函數的表達式移動到單獨的陳述式,將值儲存在變數中。在原始陳述式中,將表達式替換為對變數的引用,最佳化器可以將其視為常數值
SET @keyval = FLOOR(1 + RAND() * 49); UPDATE t SET col_a = some_expr WHERE id = @keyval;
在衍生表格中將隨機值指派給變數。此技術會導致變數在
WHERE
子句中的比較中使用之前,被指派一個值(一次)UPDATE /*+ NO_MERGE(dt) */ t, (SELECT FLOOR(1 + RAND() * 49) AS r) AS dt SET col_a = some_expr WHERE id = dt.r;
如先前所述,WHERE
子句中的非決定性表達式可能會阻止最佳化並導致表格掃描。但是,如果其他表達式是決定性的,則可能可以部分最佳化 WHERE
子句。例如
SELECT * FROM t WHERE partial_key=5 AND some_column=RAND();
如果最佳化器可以使用 partial_key
來減少選取的列集,則 RAND()
的執行次數會減少,這會降低非決定性對最佳化的影響。