优化器使用物化技术,使得子查询处理更高好。物化通过将子查询结果转化为临时表(通常是内存临时表)来加速查询执行。
在MySQL第一次需要子查询结果时,它将结果物化为临时表。之后再需要查询这个结果时,MySQL就会从临时表中查询。优化器可能会使用哈希索引来为表编排索引,从而降低查询成本,加速查询。索引包含唯一值,可以用来消除重复值,使得表更小。
子查询的物化会优先使用内存临时表,在表变得很大时,会转为磁盘存储。见章节 8.4.4,“Internal Temporary Table Use in MySQL”。
如果没有使用到物化,优化器有时会将非相关子查询转换为相关子查询。例如,下面的IN子查询是非相关的(where条件只涉及t2表中的列,不涉及t1表中的列):
select * from t1
where t1.a in (select t2.b from t2 where where_condition);
优化器可能会将其重写为EXISTS相关子查询:
select * from t1
WHERE EXISTS (SELECT t2.b FROM t2 WHERE where_condition AND t1.a=t2.b);
子查询物化使用临时表来避免这种重写,使得可以只执行一次子查询,而不是对于外查询的每一行都执行一次子查询。
要在MySQL中使用子查询物化,系统变量optimizer_switch的materialization标志必须打开。(见章节8.9.2,“Switchable Optimizations”。)materialization标志开启后,如果符合以下任意一种场景,物化适用于在任何地方出现的子查询谓词(比如,WHERE,ON,GROUP BY,HAVING,或ORDER BY):
- 谓词的表现形式如下,其中外部表达式oe_i或内部表达式ie_i都不可能是null。N>=1。
(oe_1, oe_2, ..., oe_N) [NOT] IN (SELECT ie_1, i_2, ..., ie_N ...)
- 谓词的表现形式如下,其中有一个外部表达式oe和内部表达式ie。表达式可以是null。
oe [NOT] IN (SELECT ie ...)
- 谓词是IN或NOT IN,而且带UNKNOW(NULL)的结果集有着和FALSE相同的意义。
下面的例子阐释了对UNKNOW和FALSE谓词等价评估是怎样影响是否可以使用子查询物化的。假设where_condition只涉及t2中的表,不涉及t2中的表,这样子查询就是非相关的。
适用于物化的子查询:
select * from t1
where t1.a in (select t2.b from t2 where where_condition);
这里,IN谓词返回UNKNOW或FALSE都没有关系。无论采用哪种方式,来自t1的记录不包含在查询结果中。
下面的例子中,没有使用子查询物化,t2.b是一个允许为null的列:
select * from t1
where (t1.a,t2.b) not in (select t2.a,t2.b from t2 where where_condition);
下面的限制适用于子查询物化:
- 内部表达式和外部表达式的类型必须匹配。例如,在两个表达式都是整型或都是decimal时,优化器可能会使用物化,但是如果一个是整型,一个是decimal,优化器就不能使用物化。
- 内表达式不能是BLOB类型。
使用EXPLAIN可以用来判断优化器是否使用了子查询物化:
- 相比于没有使用物化的查询执行,select_type可能会从DEPENDENT SUBQUERY变为SUBQUERY。这表明,对于一个每一条外部记录都会执行一次的子查询来说,物化使得子查询只会被执行一次。
- 对于扩展的explain输出,show warnings显示的文本输出包括materialize和materialized-subquery。
从MySQL8.0.21开始,MySQL可能会在使用[NOT] IN 或[NOT] EXISTS子查询谓词的单表UPDATE或DELETE语句中使用子查询物化(假设这个语句没有使用order by或limit),而且优化器提示或optimize_switch设置允许这种子查询物化。