规则优化

当我们执行SQL语句时MySQL会基于一些规则对语句重写,这个过程称为查询重写

搜索条件化简


  • 移除不必要的括号

    我们的语句:

      ((a = 5 AND b = c) OR ((a > c) AND (c < 5)))
    

    重写后:

      (a = 5 AND b = c) OR (a > c AND c < 5)
    
  • 常量传递constant_propagation

    我们的语句:

      a = 5 AND b > a
    

    重写后:

      a = 5 AND b > 5
    
  • 等值传递equality_propagation

    我们的语句:

      a = b and b = c and c = 1
    

    重写后:

      a = 1 and b = 1 and c = 1
    
  • 移除没用的条件trivial_condition_removal

    对于一些明显永远为TRUE或者FALSE的表达式,优化器会移除掉它们,我们的语句:

      (a < 1 and b = b) OR (a = 6 OR 5 != 5)
    

    重写后:

      a < 1 OR a = 6
    
  • 表达式计算

    在查询开始执行之前,如果表达式中只包含常量的话,它的值会被先计算出来,我们的语句:

      a = 5 + 1
    

    重写后:

      a = 6
    

    有个例外就是列并不是以单独的形式作为表达式的操作数时,例如:

      ABS(a) > 5 或者 -a < -1
    

    优化器是不会尝试对这些表达式进行化简的,只有搜索条件中索引列和常数使用某些运算符连接起来才可能使用到索引,所以最好让索引列以单独的形式出现在表达式中

  • HAVING子句和WHERE子句的合并

    如果查询语句中没有出现诸如SUM、MAX等等的聚集函数以及GROUP BY子句,优化器就把HAVING子句和WHERE子句合并起来

消除外连接


外连接和内连接的本质区别就是:对于外连接的驱动表的记录来说,如果无法在被驱动表中找到匹配ON子句中的过滤条件的记录, 那么该记录仍然会被加入到结果集中,对应的被驱动表记录的各个字段使用NULL值填充; 而内连接的驱动表的记录如果无法在被驱动表中找到匹配ON子句中的过滤条件的记录,那么该记录会被舍弃。更多关于连接可以参考这篇文章

凡是不符合WHERE子句中条件的记录都不会参与连接。只要在搜索条件中指定关于被驱动表相关列的值不为NULL,那么外连接中在被驱动表中找不到符合ON子句条件的驱动表记录也就被排除出最后的结果集了, 这种情况下外连接和内连接也就是等价的。例如:

SELECT * FROM t1 LEFT JOIN t2 ON t1.m1 = t2.m2 WHERE t2.n2 IS NOT NULL;

等价于:

SELECT * FROM t1 INNER JOIN t2 ON t1.m1 = t2.m2 WHERE t2.n2 IS NOT NULL;

或者能够表达出不为空的意思就行,例如:

SELECT * FROM t1 LEFT JOIN t2 ON t1.m1 = t2.m2 WHERE t2.m2 = 2;

等价于

SELECT * FROM t1 INNER JOIN t2 ON t1.m1 = t2.m2 WHERE t2.m2 = 2;

这种在外连接查询中,指定的WHERE子句中包含被驱动表中的列不为NULL值的条件称之为空值拒绝reject-NULL。在被驱动表的WHERE子句符合空值拒绝的条件后, 外连接和内连接可以相互转换。这种转换带来的好处就是查询优化器可以通过评估表的不同连接顺序的成本,选出成本最低的那种连接顺序来执行查询

ANY、ALL子查询优化


ANY、ALL与子查询组合时,有些场景可以转化成更简单的查询:

	< ANY (SELECT inner_expr ...)
	转换为:
	< (SELECT MAX(inner_expr) ...)

	> ANY (SELECT inner_expr ...)
	转换为:
	> (SELECT MIN(inner_expr) ...)

	< ALL (SELECT inner_expr ...)	
	转换为:
	< (SELECT MIN(inner_expr) ...)
	
	
	> ALL (SELECT inner_expr ...)	
	转换为:
	> (SELECT MAX(inner_expr) ...)

[NOT] EXISTS子查询的执行


不相关子查询

如果[NOT] EXISTS子查询是不相关子查询,可以先执行子查询,得出该[NOT] EXISTS子查询的结果是TRUE还是FALSE,并重写原先的查询语句,例如:

SELECT * FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.n1 = 100) OR t1.n1 > 100;

那么步骤如下:

  1. 先执行子查询,取其结果TRUE 还是 FALSE
  2. 简化查询

    若为TRUE

     SELECT * FROM t1 WHERE TRUE OR t1.n1 > 100;
     SELECT * FROM t1 WHERE TRUE;
    

    若为FALSE

     SELECT * FROM t1 WHERE FALSE OR t1.n1 > 100;
     SELECT * FROM t1 WHERE t1.n1 > 100;
    

相关子查询只能按照相关子查询的方式执行,相关子查询的执行方式参考这篇文章

派生表优化


把子查询放在外层查询的FROM子句后,那么这个子查询的结果相当于一个派生表,对于派生表有两种优化策略:

  • 派生表物化

    关于物化参考这篇文章

  • 将派生表和外层的表合并

    将查询重写为没有派生表的形式,例如:

      SELECT * FROM (SELECT * FROM t1 WHERE c1 = 10) AS derived_t1 INNER JOIN t2 ON derived_t1.n1 = t2.n1 WHERE t2.n2 = 1;
    	
      重写后:
    	
      SELECT * FROM t1 INNER JOIN t2 ON t1.n1 = t2.n1 WHERE t1.c1 = 10 AND t2.n2 = 1;
    

这样通过将外层查询和派生表合并的方式成功的消除了派生表,没必要再付出创建和访问临时表的成本了,当派生表中有这些语句不可以和外层查询合并:

  • 聚集函数
  • DISTINCT
  • GROUP BY
  • HAVING
  • LIMIT
  • UNION 或者 UNION ALL
  • 派生表对应的子查询的SELECT子句中含有另一个子查询

MySQL在执行带有派生表的查询时,优先尝试把派生表和外层查询合并掉,如果不行的话,再把派生表物化执行查询