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()
的執行次數會減少,這會降低非決定性對最佳化的影響。