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);
这两个查询都看起来使用主键查找,因为是对主键的等值比较,但是只有第一个查询是真的:
非确定性的影响不仅限于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()
函数执行次数更少,这将减少非确定性对优化的影响。