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


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

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