文件首頁
MySQL 8.4 參考手冊
相關文件 下載本手冊
PDF (美式信紙) - 39.9Mb
PDF (A4) - 40.0Mb
Man Pages (TGZ) - 258.5Kb
Man Pages (Zip) - 365.5Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 8.4 參考手冊  /  ...  /  函數呼叫最佳化

10.2.1.20 函數呼叫最佳化

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

由於與主鍵的相等比較,這兩個查詢似乎都使用了主鍵查詢,但這只適用於第一個查詢

  • 第一個查詢總是產生最多一列,因為具有常數引數的 POW() 是常數值,並用於索引查詢。

  • 第二個查詢包含一個使用非決定性函數 RAND() 的表達式,該函數在查詢中不是常數,事實上,對於表格 t 的每一列都有一個新值。因此,該查詢會讀取表格的每一列,評估每一列的述詞,並輸出主鍵與隨機值匹配的所有列。這可能是零列、一列或多列,取決於 id 列的值和 RAND() 序列中的值。

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