本篇文章将会结合源码介绍在MySQL中针对子查询的几种优化策略。

对于表示是否存在语义的查询语句,在语法上表示为IN/=ANY/EXISTS,优化器会尝试转换为semijoin/antijoin进行优化。与普通join会将左表和右表的记录连接在一起不同,semijoin/antijoin仅关心右表中是否存在可以与左表记录连接的记录,而返回左表记录。

在prepare阶段,优化器会首先检查当前查询是否可以转换为semijoin/antijoin的条件(由于antijoin是semijoin的相反,在代码层面也是一块处理的,所以之后的论述以semijoin为主),这部分代码在中,具体的条件总结如下:

  1. 子查询必须是谓词IN/=ANY/EXISTS的一部分,并且出现在WHERE或ON语法的最高层,可以被包含在AND表达式中。
  2. 必须是单个查询块,不带有UNION。
  3. 不包含HAVING语法。
  4. 不包含LIMIT语法。
  5. 外查询语句没有使用STRAIGHT_JOIN语法。

由于优化器对查询块的处理是一种递归的方式,在完成对子查询的判断之后,在外层查询的prepare阶段,会调用函数完成子查询到semijoin的最终转换,这个过程在整个查询的生命周期只会发生一次,且不可逆。在SQL语法上等价为:

为了实现上述过程,需要进行以下步骤:

  1. 创建语以部分,并加入到外层查询块的执行计划中。
  2. 将子查询谓词从父查询的判断谓词中消除。

具体的伪代码如下:

对于不能采用semijoin/antijoin执行的存在式语义的子查询,在MySQL源码的表示含义下,会做IN->EXISTS的转换,其实本质是在物化执行和迭代式循环执行中做选择。IN语法代表非相关子查询仅执行一次,将查询结果物化成临时表,之后需要结果时候就去物化表中查找;EXISTS代表对于外表的每一条记录,子查询都会执行一次,是迭代式循环执行。

在prepare阶段IN->EXISTS的转换主要是将IN语法的左表达式与右表达式中子查询的输出列对应组合,加入到子查询的WHERE或者HAVING条件中,在SQL语义上表示为:

这一过程主要发生在中,详细过程为:

以上就是MySQL中针对子查询所做的大部分优化和转换的工作,代码分析基于MySQL 8.0.19版本。

参考:https://dev.mysql.com/doc/refman/8.0/en/subquery-optimization.html