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()
执行的次数就会减少,从而降低非确定性对优化的影响。