MySQL查询优化之函数调用的优化

原文地址:https://dev.mysql.com/doc/refman/5.7/en/function-optimization.html

译文:

8.2.1.18 函数调用优化

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);

从与主键的相等性比较可以看出,这两个查询似乎都使用了主键查找,但只有第一个查询使用了主键查找:

    1)第一个查询总是生成最多一行,因为参数为常数的POW()函数值是一个常数值,用于索引查找。

    2)第二个查询包含了一个使用非确定性函数RAND()的表达式,该表达式不是一个常数,但实际上对于表t中的每一行,它都会有一个新值与之对应。因此,查询读取表中的每一行,计算每一行的谓词,并输出主键与随机值(由表达式产生)匹配的所有行。根据id列值和RAND()序列中的值,这可能是0、1或多个行。

不确定性的影响并不局限于SELECT语句。下面这个UPDATE语句使用一个非确定性函数来选择要修改的行:

  • UPDATE t SET col_a = some_expr WHERE id = FLOOR(1 + RAND() * 49);

可能的目的是最多只更新主键与表达式匹配的一行。但是,根据id列值和RAND()序列中的值,它可能更新0、1或多个行。

刚才描述的行为对性能和复制有影响:

    1)由于不确定性函数不产生常量值,优化器不能使用其他函数可能适用的策略,例如索引查找。结果可能是表扫描;

    2)InnoDB可能会升级为一个范围键锁,而不是为一个匹配的行使用单个行锁;

    3)不确定执行的更新对于复制是不安全的。

这些困难源于这样一个事实,即RAND()函数对表的每一行求值一次。为了避免多重函数计算,可以使用以下技术之一:

    1)将包含非确定性函数的表达式移动到单独的语句中,将值保存在变量中。在最初的语句中,将表达式替换为一个引用变量,优化器可以将其视为一个常数值:

  • SET @keyval = FLOOR(1 + RAND() * 49);
    UPDATE t SET col_a = some_expr WHERE id = @keyval;

    2)将随机值赋给派生表中的变量。这种技术使变量在WHERE子句的比较中使用之前被赋值一次:

  • SET optimizer_switch = 'derived_merge=off';
    UPDATE t, (SELECT @keyval := FLOOR(1 + RAND() * 49)) AS dt
    SET col_a = some_expr WHERE id = @keyval;

如前所述,WHERE子句中的不确定性表达式可能会阻止优化并导致表扫描。但是,如果其他表达式是确定的,则可以部分优化WHERE子句。例如:

  • SELECT * FROM t WHERE partial_key=5 AND some_column=RAND();

如果优化器可以使用partial_key来减少所选择的行集,那么RAND()的执行次数就会减少,这就减小了不确定性对优化的影响。

PS:由于水平有限,译文中难免会存在谬误,欢迎批评指正。

猜你喜欢

转载自blog.csdn.net/qq_41080850/article/details/85673553