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);
这两个查询似乎都使用主键查找,因为它们都使用了主键的相等比较,但这只是第一个查询的情况:
非确定性的影响不仅限于 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()
将被执行更少次,这减少了非确定性的影响对优化的影响。