文档首页
MySQL 9.0 参考手册
相关文档 下载此手册
PDF(US Ltr) - 40.0Mb
PDF(A4) - 40.1Mb
手册页(TGZ) - 258.2Kb
手册页(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() 执行的次数就会减少,从而降低非确定性对优化的影响。