函数调用优化
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()的执行次数较少,这降低了非确定性对优化的影响。