Documentation Home
MySQL 8.4 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 39.8Mb
PDF (A4) - 39.9Mb
Man Pages (TGZ) - 257.9Kb
Man Pages (Zip) - 364.9Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 8.4 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;
  • 将随机值分配给派生表中的变量。这技术使得变量在比较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()函数执行次数更少,这将减少非确定性对优化的影响。