Documentation Home
MySQL 8.3 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 40.8Mb
PDF (A4) - 40.9Mb
Man Pages (TGZ) - 294.0Kb
Man Pages (Zip) - 409.0Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb
Excerpts from this Manual

MySQL 8.3 Reference Manual  /  ...  /  Function Call Optimization

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;
  • 将随机值分配给一个变量,在一个派生表中。这项技术使变量在比较之前被赋值一次:

    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() 将被执行更少次,这减少了非确定性的影响对优化的影响。