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;
在派生表中将随机值赋给一个变量。这种技术会导致在
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()
执行的次数会更少,这会降低非确定性对优化的影响。