【数据库内核】基于规则优化之子查询优化

目录

概述

准备资料

子查询的分类

从子查询出现在SQL语句的位置区分

一、目标列位置

二、FORM 子句位置

三、WHERE 子句位置

四、JOIN/ON 子句位置

五、GroupBy子句位置

六、OrderBy子句位置

从对象间的关系区分

一、相关子查询

二、非相关子查询

从特定谓词区分

一、[NOT] IN/ALL/ANY/SOME子查询

二、[NOT] EXISTS子查询

三、其他的子查询

从语句的构成复杂程度区分

一、SPJ子查询

二、GROUPBY子查询

三、其他子查询

从结果的角度区分

一、标量子查询

二、列子查询

三、行子查询

四、表子查询

为什么要做子查询优化

子查询优化方式

一、子查询合并(Subquery Coalescing)

二、子查询展开(Subquery Unnesting)

三、最常见的子查询优化策略

IN 子查询优化

NOT IN 子查询优化

ANY 或 SOME 子查询优化

ALL 子查询优化

EXISTS 子查询优化

NOT EXISTS 子查询优化

TIDB子查询优化方式

一、... < ALL (SELECT ... FROM ...) 或者 ... > ANY (SELECT ... FROM ...)

二、... != ANY (SELECT ... FROM ...)

三、... = ALL (SELECT ... FROM ...)

四、... IN (SELECT ... FROM ...)

五、EXISTS 子查询以及 ... >/>=/

PostgreSQL子查询优化方式

一、提升子链

In的提升子链的情况

Exists的提升子链的情况

二、提升子查询

三、子查询等价改写

Mysql子查询优化方式

一、情况1

二、情况2

三、情况3

结论

参考文档


概述

 

当一个查询是另一个查询的子部分时,称之为子查询(查询语句中嵌套有查询语句)。

 

准备资料

 

1.SQL中各种Join的描述信息,请参考之前的《【数据库】Join的种类和用法》

2.SQL表的信息

create table t_subquery(c1 int, c2 int)insert into t_subquery values(1,11)insert into t_subquery values(2,22)insert into t_subquery values(null,null)drop table left_tablecreate table left_table(id int, name varchar(20))insert into left_table values(1, 'Dog')insert into left_table values(2, 'Cat')insert into left_table values(3, 'Pig')insert into left_table values(null,null)drop table right_tablecreate table right_table(id int, name varchar(20))insert into right_table values(1, 'Squirrel')insert into right_table values(2, 'Rabbit')insert into right_table values(4, 'Whale')insert into right_table values(null,null)

 

子查询的分类

 

从子查询出现在SQL语句的位置区分

一、目标列位置

子查询如果位于目标列,则只能为标量子查询,否则数据库可能返回类似“错误:子查询必须只能返回一个字段”的提示。

 

SQL语句信息:

SELECT (SELECT COUNT(*) FROM T_SUBQUERY) FROM DUAL

 

Oracle中的执行结果:

 

二、FORM 子句位置

相关子查询出现在FROM子句中,数据库可能返回类似“在FROM子句中的子查询无法参考相同查询级别中的关系”的提示,所以相关子查询不能出现在FROM子句中;非相关子查询出现在FROM子句中,可上拉子查询到父层,在多表连接时统一考虑连接代价然后择优。

 

SQL语句信息:

SELECT * FROM (SELECT C1 FROM T_SUBQUERY)

 

Oracle中的执行结果:

 

三、WHERE 子句位置

出现在WHERE子句中的子查询,是一个条件表达式的一部分,而表达式可以分解为操作符和操作数;根据参与运算的不同的数据类型,操作符也不尽相同,如INT类型有“<、>、=、<>”等操作,这对子查询均有一定的要求(如INT型的等值操作,要求查询必须是标量子查询)。另外,子查询出现在WHERE子句中的格式,也有用谓词指定的一些操作,如IN、BETWEEN、EXISTS等。

 

SQL语句信息:

SELECT * FROM DUAL WHERE 12 > any(SELECT C1 FROM T_SUBQUERY)

 

Oracle中的执行结果:

 

四、JOIN/ON 子句位置

JOIN/ON子句可以拆分为两部分,一是JOIN块类似于FROM子句,二是ON子句块类似于WHERE子句,这两部分都可以出现子查询。子查询的处理方式同FROM子句和WHERE子句。

 

SQL语句信息:

SELECT  * FROM  DUAL T1  JOIN ( SELECT C1 AS C1 FROM T_SUBQUERY ) T2 ON T1.dummy = cast(  T2.C1 AS VARCHAR ( 10 ))

 

Oracle中的执行结果:

 

五、GroupBy子句位置

目标列必须和GROUPBY关联.可将子查询写在GROUPBY位置处,但子查询用在GROUPBY处没有实用意义。并且Oracle不支持主查询的Group by后面跟着子查询。

SQL语句信息:

SELECT  MAX(1) FROM  DUALGROUP BY   (SELECT COUNT(C1) AS COUNT FROM T_SUBQUERY)

 

Oracle中的执行结果:

 

六、OrderBy子句位置

可将子查询写在ORDERBY位置处,但ORDERBY操作是作用在整条SQL语句上的,子查询用在ORDERBY处没有实用意义。

SQL语句信息:

SELECT  1 FROM  DUALORDER BY   (SELECT COUNT(C1) AS COUNT FROM T_SUBQUERY)

 

Oracle中的执行结果:

 

从对象间的关系区分

一、相关子查询

子查询的执行依赖于外层父查询的一些属性值。子查询因依赖于父查询的参数,当父查询的参数改变时,子查询需要根据新参数值重新执行(查询优化器对相关子查询进行优化有一定意义),如:

SELECT * FROM t1 WHERE col_1 = ANY (SELECT col_1 FROM t2 WHERE t2.col_2 = t1.col_2);# 子查询语句中存在父查询的t1表的col_2列

注:

ANY关键词可以理解为“对于子查询返回的列中的任一数值,如果比较结果为true,则返回true”。 

ALL 的意思是“对于子查询返回的列中的所有值,如果比较结果为true,则返回true”。

 

二、非相关子查询

子查询的执行,不依赖于外层父查询的任何属性值。这样子查询具有独立性,可独自求解,形成一个子查询计划先于外层的查询求解,如:

SELECT * FROM t1 WHERE col_1 = ANY(SELECT col_1 FROM t2 WHERE t2.col_2 = 10);//子查询语句中(t2)不存在父查询(t1)的属性

 

从特定谓词区分

一、[NOT] IN/ALL/ANY/SOME子查询

语义相近,表示“[取反] 存在/所有/任何/任何”,左面是操作数,右面是子查询,是最常见的子查询类型之一。

 

二、[NOT] EXISTS子查询

半连接语义,表示“[取反] 存在”,没有左操作数,右面是子查询,也是最常见的子查询类型之一。

 

三、其他的子查询

除了上述两种外的所有子查询。

 

从语句的构成复杂程度区分

一、SPJ子查询

由选择、连接、投影操作组成的查询。(SELECT(选择), PROJECT(投影), JOIN(连接))。

 

二、GROUPBY子查询

SPJ子查询加上分组、聚集操作组成的查询。

 

三、其他子查询

GROUPBY子查询中加上其他子句如Top-N 、LIMIT/OFFSET、集合、排序等操作。

后两种子查询有时合称非SPJ子查询。

 

从结果的角度区分

一、标量子查询

子查询返回的结果集类型是一个单一值。

 

二、列子查询

子查询返回的结果集类型是一条单一元祖。(return a single row)

 

三、行子查询

子查询返回的结果集类型是一个单一列。(return a single column)

 

四、表子查询

子查询返回的结果集类型是一个表(多行多列)。

 

为什么要做子查询优化

 

在数据库实现早期,查询优化器对子查询一般采用嵌套执行的方式,即对父查询中的每一行都执行一次子查询,这样子查询会执行很多次。这种执行方式效率很低。

而对子查询进行优化,可能带来几个数量级的查询效率的提高。子查询转变成为连接操作之后,会得到如下好处:

  1. 子查询不用执行很多次。
  2. 优化器可以根据统计信息来选择不同的连接方法和不同的连接顺序。
  3. 子查询中的连接条件、过滤条件分别变成了父查询的连接条件、过滤条件,优化器可以对这些条件进行下推,以提高执行效率。

举个例子:

下面图中的SQL是未经去关联化的原始查询计划(Relation Tree)。与其他查询计划不一样的是,我们特地画出了表达式树(Expression Tree),可以清晰地看到:子查询是实际上是挂在 Filter 的条件表达式下面的。

 

实际执行时,查询计划执行器(Executor)在执行到 Filter 时,调用表达式执行器(Evaluator);由于这个条件表达式中包含一个标量子查询,所以 Evaluator 又会调用 Executor 计算标量子查询的结果。

这种 Executor - Evaluator - Executor 的交替调用十分低效!考虑到 Filter 上可能会有上百万行数据经过,如果为每行数据都执行一次子查询,那查询执行的总时长显然是不可接受的。

我们把这个SQL改写成Join的表达式形式

通过改写大大的提升了计算的性能。所以通常情况下子查询的优化是非常有必要的。

 

子查询优化方式

 

一、子查询合并(Subquery Coalescing)

在某些条件下(语义等价:两个查询块产生同样的结果集),多个子查询能够合并成一个子查询(合并后还是子查询,以后可以通过其他技术消除掉子查询)。这样可以把多次表扫描、多次连接减少为单次表扫描和单次连接,如:

SELECT * FROM LEFT_TABLE LT WHERE     EXISTS (SELECT RT.ID FROM RIGHT_TABLE RT WHERE RT.ID >= 1 AND LT.ID = RT.ID) OR     EXISTS (SELECT RT.ID FROM RIGHT_TABLE RT WHERE RT.ID >= 1 AND LT.ID = RT.ID)

 

Oracle计划如下图所示:

 

SQL可优化为:

SELECT * FROM LEFT_TABLE LT WHERE     EXISTS (SELECT RT.ID FROM RIGHT_TABLE RT WHERE RT.ID >= 1 AND LT.ID = RT.ID)     # 两个ESISTS子句合并为一个,条件也进行了合并);

 

Oracle计划如下图所示:

 

二、子查询展开(Subquery Unnesting)

又称子查询反嵌套,又称为子查询上拉。把一些子查询置于外层的父查询中,作为连接关系与外层父查询并列,其实质是把某些子查询重写为等价的多表连接操作(展开后,子查询不存在了,外部查询变成了多表连接)。带来的好处是,有关的访问路径、连接方法和连接顺序可能被有效使用,使得查询语句的层次尽可能的减少。

 

常见的IN/ANY/SOME/ALL/EXISTS依据情况转换为半连接(SEMI JOIN)、普通类型的子查询消除等情况属于此类,如:

SELECT  * FROM  LEFT_TABLE LT,  ( SELECT * FROM RIGHT_TABLE RT WHERE RT.ID > 1 ) RT WHERE  LT.ID < 10   AND RT.ID <= 4

 

Oracle计划显示如下图所示:

 

SQL可优化为:

SELECT  * FROM  LEFT_TABLE LT,  RIGHT_TABLE RT WHERE  LT.ID < 10   AND RT.ID <= 4   AND RT.ID > 1;/* 子查询变为了LEFT_TABLE、RIGHT_TABLE表的连接操作,相当于把RIGHT_TABLE表从子查询中上拉了一层 */

 

Oracle计划显示如下图所示:

 

子查询展开是一种最为常用的子查询优化技术,子查询展开有以下两种形式:

  1. 如果子查询中出现了聚集、GROUPBY、DISTINCT子句,则子查询只能单独求解,不可以上拉到外层。
  2. 如果子查询只是一个简单格式的(SPJ格式)查询语句,则可以上拉子查询到外层,这样往往能提高查询效率。子查询上拉,讨论的就是这种格式,这也是子查询展开技术处理的范围。

 

把子查询上拉到上层查询,前提是上拉(展开)后的结果不能带来多余的元组,所以子查询展开需要遵循如下规则:

  1. 如果上层查询的结果没有重复(即SELECT子句中包含主码),则可以展开其子查询。并且展开后的查询的SELECT子句前应加上DISTINCT标志。
  2. 如果上层查询的SELECT语句中有DISTINCT标志,可以直接进行子查询展开。
  3. 如果内层查询结果没有重复元组,则可以展开。

 

子查询展开的具体步骤:

  1. 将子查询和外层查询的FROM子句连接为同一个FROM子句,并且修改相应的运行参数。
  2. 将子查询的谓词符号进行相应修改(如:IN修改为=ANY)。
  3. 将子查询的WHERE条件作为一个整体与外层查询的WHERE条件合并,并用AND条件连接词连接,从而保证新生成的谓词与原谓词的上下文意思相同,且成为一个整体。

 

三、最常见的子查询优化策略

子查询的格式有很多,常见的子查询有In类型、ALL/SOME/ANY类型、EXISTS类型。下面我们就基于这三种常见类型对子查询优化是如何做的进行简单介绍。

 

IN 子查询优化

SQL写法如下所示:

SELECT  * FROM  LEFT_TABLE LT WHERE  LT.ID IN ( SELECT ID FROM RIGHT_TABLE )

 

Oracle计划显示如下图:

 

总结:

通过Oracle计划可以看到,Oracle把in操作优化成了Semi Join。

 

NOT IN 子查询优化

SQL写法如下所示:

SELECT  * FROM  LEFT_TABLE LT WHERE  LT.ID NOT IN ( SELECT ID FROM RIGHT_TABLE )

 

Oracle计划显示如下图:

 

总结:

通过Oracle计划可以看到,Oracle把not in操作优化成了Anti NA Join。

 

ANY 或 SOME 子查询优化

1. > Any 或者 > Some 场景

SQL写法如下所示:

 

SELECT  * FROM  LEFT_TABLE LT WHERE  LT.ID > ANY ( SELECT ID FROM RIGHT_TABLE )

 

Oracle计划显示如下图:

 

总结:

通过Oracle计划可以看到,Oracle把any操作优化成了Semi Join。

 

2.  = Any 或者 = Some 场景

SQL写法如下所示:

SELECT  * FROM  LEFT_TABLE LT WHERE  LT.ID = ANY ( SELECT ID FROM RIGHT_TABLE )

 

Oracle计划显示如下图:

 

总结:

通过Oracle计划可以看到,Oracle把等值的any操作优化成了Semi Join。

 

3. != Any 或者 != Some 场景

SQL写法如下所示:

 

SELECT  * FROM  LEFT_TABLE LT WHERE  LT.ID != ANY ( SELECT ID FROM RIGHT_TABLE )

 

Oracle计划显示如下图:

 

总结:

通过Oracle计划可以看到,Oracle把不等于的any操作优化成了Exists运算。

 

ALL 子查询优化

1. 等于 ALL 场景

SQL写法如下所示:

 

SELECT  * FROM  LEFT_TABLE LT WHERE  LT.ID = ALL ( SELECT ID FROM RIGHT_TABLE )

 

Oracle计划显示如下图:

 

总结:

通过Oracle计划可以看到,Oracle把等于的all操作优化成了Exists运算。

 

2. 不等于 ALL 场景

SQL写法如下所示:

 

SELECT  * FROM  LEFT_TABLE LT WHERE  LT.ID != ALL ( SELECT ID FROM RIGHT_TABLE )

 

Oracle计划显示如下图:

 

总结:

通过Oracle计划可以看到,Oracle把不等于的all操作优化成了Anti NA Join计算。

 

3. 大于 ALL 场景

SQL写法如下所示:

SELECT  * FROM  LEFT_TABLE LT WHERE  LT.ID > ALL ( SELECT ID FROM RIGHT_TABLE )

 

Oracle计划显示如下图:

 

总结:

通过Oracle计划可以看到,Oracle把大于all操作优化成了Anti NA Join计算。

 

EXISTS 子查询优化

SQL写法如下所示:

 

SELECT  * FROM  LEFT_TABLE LTWHERE  EXISTS(SELECT RT.ID FROM RIGHT_TABLE RT WHERE LT.ID = RT.ID)

 

Oracle计划显示如下图:

 

总结:

通过Oracle计划可以看到,Oracle把EXISTS操作优化成了Semi Join。

 

NOT EXISTS 子查询优化

SQL写法如下所示:

 

SELECT  * FROM  LEFT_TABLE LTWHERE  NOT EXISTS(SELECT RT.ID FROM RIGHT_TABLE RT WHERE LT.ID = RT.ID)

 

Oracle计划显示如下图:

 

总结:

通过Oracle计划可以看到,Oracle把not exists 操作优化成了Anti Join。

 

TIDB子查询优化方式

 

通常会遇到如下情况的子查询:

  • NOT IN (SELECT ... FROM ...)
  • NOT EXISTS (SELECT ... FROM ...)
  • IN (SELECT ... FROM ..)
  • EXISTS (SELECT ... FROM ...)
  • ... >、>=、<、<=、=、!= (SELECT ... FROM ...)

 

有时,子查询中包含了非子查询中的列,如 select * from t where t.a in (select * from t2 where t.b=t2.b) 中,子查询中的 t.b 不是子查询中的列,而是从子查询外面引入的列。这种子查询通常会被称为关联子查询,外部引入的列会被称为关联列,关联子查询相关的优化参见关联子查询去关联。本文主要关注不涉及关联列的子查询。

子查询默认会以理解 TiDB 执行计划中提到的 semi join 作为默认的执行方式,同时对于一些特殊的子查询,TiDB 会做一些逻辑上的替换使得查询可以获得更好的执行性能。

 

一、... < ALL (SELECT ... FROM ...) 或者 ... > ANY (SELECT ... FROM ...)

对于这种情况,可以将 ALL 或者 ANY 用 MAX 以及 MIN 来代替。不过由于在表为空时,MAX(EXPR) 以及 MIN(EXPR) 的结果会为 NULL,其表现形式和 EXPR 是有 NULL 值的结果一样。以及外部表达式结果为 NULL 时也会影响表达式的最终结果,因此这里完整的改写会是如下的形式:

  • t.id < all(select s.id from s) 会被改写为 t.id < min(s.id) and if(sum(s.id is null) != 0, null, true)。
  • t.id < any (select s.id from s) 会被改写为 t.id < max(s.id) or if(sum(s.id is null) != 0, null, false)。

 

二、... != ANY (SELECT ... FROM ...)

对于这种情况,当子查询中不同值的各种只有一种的话,那只要和这个值对比就即可。如果子查询中不同值的个数多于 1 个,那么必然会有不相等的情况出现。因此这样的子查询可以采取如下的改写手段:

  • select * from t where t.id != any (select s.id from s) 会被改写为 select t.* from t, (select s.id, count(distinct s.id) as cnt_distinct from s) where (t.id != s.id or cnt_distinct > 1)

 

三、... = ALL (SELECT ... FROM ...)

对于这种情况,当子查询中不同值的个数多于一种的话,那么这个表达式的结果必然为假。因此这样的子查询在 TiDB 中会改写为如下的形式:

  • select * from t where t.id = all (select s.id from s) 会被改写为 select t.* from t, (select s.id, count(distinct s.id) as cnt_distinct from s) where (t.id = s.id and cnt_distinct <= 1)

 

四、... IN (SELECT ... FROM ...)

对于这种情况,会将其改写为 IN 的子查询改写为 SELECT ... FROM ... GROUP ... 的形式,然后将 IN 改写为普通的 JOIN 的形式。如 select * from t1 where t1.a in (select t2.a from t2) 会被改写为 select t1.* from t1, (select distinct(a) a from t2) t2 where t1.a = t2.a 的形式。同时这里的 DISTINCT 可以在 t2.a 具有 UNIQUE 属性时被自动消去。

 

explain select * from t1 where t1.a in (select t2.a from t2);
+------------------------------+---------+-----------+------------------------+----------------------------------------------------------------------------+| id                           | estRows | task      | access object          | operator info                                                              |+------------------------------+---------+-----------+------------------------+----------------------------------------------------------------------------+| IndexJoin_12                 | 9990.00 | root      |                        | inner join, inner:TableReader_11, outer key:test.t2.a, inner key:test.t1.a || ├─HashAgg_21(Build)          | 7992.00 | root      |                        | group by:test.t2.a, funcs:firstrow(test.t2.a)->test.t2.a                   || │ └─IndexReader_28           | 9990.00 | root      |                        | index:IndexFullScan_27                                                     || │   └─IndexFullScan_27       | 9990.00 | cop[tikv] | table:t2, index:idx(a) | keep order:false, stats:pseudo                                             || └─TableReader_11(Probe)      | 1.00    | root      |                        | data:TableRangeScan_10                                                     ||   └─TableRangeScan_10        | 1.00    | cop[tikv] | table:t1               | range: decided by [test.t2.a], keep order:false, stats:pseudo              |+------------------------------+---------+-----------+------------------------+----------------------------------------------------------------------------+

这个改写会在 IN 子查询相对较小,而外部查询相对较大时产生更好的执行性能。因为不经过改写的情况下,我们无法使用以 t2 为驱动表的 index join。同时这里的弊端便是,当改写删成的聚合无法被自动消去且 t2 表比较大时,反而会影响查询的性能。目前 TiDB 中使用 tidb_opt_insubq_to_join_and_agg 变量来控制这个优化的打开与否。当遇到不合适这个优化的情况可以手动关闭。

 

五、EXISTS 子查询以及 ... >/>=/</<=/=/!= (SELECT ... FROM ...)

当前对于这种场景的子查询,当它不是关联子查询时,TiDB 会在优化阶段提前展开它,将其直接替换为一个结果集直接判断结果。如下图中,EXISTS 会提前在优化阶段被执行为 TRUE,从而不会在最终的执行结果中看到它。

 

create table t1(a int);create table t2(a int);insert into t2 values(1);explain select * from t where exists (select * from t2);

 

+------------------------+----------+-----------+---------------+--------------------------------+| id                     | estRows  | task      | access object | operator info                  |+------------------------+----------+-----------+---------------+--------------------------------+| TableReader_12         | 10000.00 | root      |               | data:TableFullScan_11          || └─TableFullScan_11     | 10000.00 | cop[tikv] | table:t       | keep order:false, stats:pseudo |+------------------------+----------+-----------+---------------+--------------------------------+

 

PostgreSQL子查询优化方式

 

postgreSQL对于子查询有两种描述,子查询和子链接。

子查询和子链接区别:子查询是不在表达式中的子句,子链接在表达式中的子句。

 

一、提升子链

 

In的提升子链的情况

目标是将IN和exists子句递归提升。

 

select * from t1 where t1.a1 in (select t2.a2 from t2 where t2.b2 = 10);

假设t2.a2为unique 转化为:

 

select t1.a1,t1,a2 from t1 join t2 where t1.a1=t2.a2 and t2.b2 = 10;

in子链接执行计划如下:

 

postgres=> explain select * from t1 where t1.a1 in (select t2.a2 from t2 where t2.b2 = 10);                                QUERY PLAN-------------------------------------------------------------------------- Nested Loop  (cost=0.28..25.80 rows=1 width=8)   ->  Seq Scan on t2  (cost=0.00..17.50 rows=1 width=4)         Filter: (b2 = 10)   ->  Index Scan using t1_a1_key on t1  (cost=0.28..8.29 rows=1 width=8)         Index Cond: (a1 = t2.a2)

 

Exists的提升子链的情况

select * from t1 where exists (select t2.a2 from t2 where t2.a2 = t1.a1) ;

 

假设t2.a2为unique 转化为:

 

select t1.a1, t1.b1 from t1, t2 where t1.a1=t2.a1;

 

exists子链接执行计划如下:

 

postgres=> explain select * from t1 where exists  (select t2.a2 from t2 where t2.a2 = t1.a1) ;                           QUERY PLAN----------------------------------------------------------------- Hash Join  (cost=26.42..54.69 rows=952 width=8)   Hash Cond: (t2.a2 = t1.a1)   ->  Seq Scan on t2  (cost=0.00..15.00 rows=1000 width=4)   ->  Hash  (cost=14.52..14.52 rows=952 width=8)         ->  Seq Scan on t1  (cost=0.00..14.52 rows=952 width=8)(5 rows)

 

二、提升子查询

子查询和子链接区别:子查询不在表达式中子句,子链接在in/exists表达式中的子句。

 

select * from t1, (select * from t2) as c where t1.a1 = c.a2;

转化为:

 

select * from t1, t2 where t1.a1 = t2.a2;

 

计划显示如下:

 

postgres=> explain select * from t1, (select * from t2) as c  where  t1.a1 = c.a2;                           QUERY PLAN----------------------------------------------------------------- Hash Join  (cost=26.42..54.69 rows=952 width=16)   Hash Cond: (t2.a2 = t1.a1)   ->  Seq Scan on t2  (cost=0.00..15.00 rows=1000 width=8)   ->  Hash  (cost=14.52..14.52 rows=952 width=8)         ->  Seq Scan on t1  (cost=0.00..14.52 rows=952 width=8)(5 rows)

 

并不是所有的子查询都能提升,含有集合操作、聚合操作、sort/limit/with/group、易失函数、from为空等是不支持提升的。如下:

 

postgres=> explain select t1.a1 from t1, (select a2 from t2 limit 1) as c where c.a2 = 10;                               QUERY PLAN------------------------------------------------------------------------ Nested Loop  (cost=0.00..24.07 rows=952 width=4)   ->  Subquery Scan on c  (cost=0.00..0.03 rows=1 width=0)         Filter: (c.a2 = 10)         ->  Limit  (cost=0.00..0.01 rows=1 width=4)               ->  Seq Scan on t2  (cost=0.00..15.00 rows=1000 width=4)   ->  Seq Scan on t1  (cost=0.00..14.52 rows=952 width=4)(6 rows)

 

三、子查询等价改写

子查询中没有group by子句,也没有聚集函数,的场景下,则可使用下面的等价转换

  • VAL>ALL(SELECT...) TO VAL>MAX(SELECT...)

  • VAL<ALL(SELECT...) TO VAL<MIN(SELECT...)

  • VAL>ANY(SELECT...) TO VAL>MIN(SELECT...)

  • VAL<ANY(SELECT...) TO VAL<MAX(SELECT...)

  • VAL>=ALL(SELECT...) TO VAL>=MAX(SELECT...)

  • VAL<=ALL(SELECT...) TO VAL<=MIN(SELECT...)

  • VAL>=ANY(SELECT...) TO VAL>=MIN(SELECT...)

  • VAL<=ANY(SELECT...) TO VAL<=MAX(SELECT...)

通常,聚集函数MIN(), MAX()的执行效率要比any、all效率高.

 

Mysql子查询优化方式

 

一、情况1

 

SELECT * FROM t1WHERE s1 IN (SELECT s1 FROM t1) OR s1 IN (SELECT s1 FROM t2);

优化后的SQL:

SELECT * FROM t1WHERE s1 IN (SELECT s1 FROM t1 UNION ALL SELECT s1 FROM t2);

 

二、情况2

SELECT (SELECT column1 FROM t1) + 5 FROM t2;

优化后的SQL:

SELECT (SELECT column1 + 5 FROM t1) FROM t2;

 

三、情况3

SELECT  * FROM  t1 WHERE  EXISTS ( SELECT * FROM t2 WHERE t2.column1 = t1.column1 AND t2.column2 = t1.column2 );
优化后的SQL:
SELECT * FROM t1  WHERE (column1,column2) IN (SELECT column1,column2 FROM t2);

 

结论

 

子查询(Subquery)的优化一直以来都是 SQL 查询优化中的难点之一。关联子查询的基本执行方式类似于 Nested-Loop,但是这种执行方式的效率常常低到难以忍受。当数据量稍大时,必须在优化器中对其进行去关联化(Decoorelation 或 Unnesting),将其改写为类似于 Semi-Join 这样的更高效的算子。

 

参考文档

  1. <<数据库查询优化器的艺术>>
  2. https://pingcap.com/blog-cn/tidb-optimization-for-subquery/
  3. <<高性能MySQL>>
分享大数据行业的一些前沿技术和手撕一些开源库的源代码
微信公众号名称:技术茶馆
微信公众号ID    :    Night_ZW
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值