您的位置:首页 > 数据库 > MySQL

MySQL子查询优化---目标列中的子查询执行情况分析

2017-03-13 19:05 363 查看
问题:

在目标列处的子查询mysql优化分析器会进行什么优化吗?比如我的主sql返回1000条数据,那么目标列处的子查询会被执行1000次吗?

分析:

其实这个问题看似简单,却不容易回答。这里,包括了子查询的知识,包括了优化器的知识,还包括了执行器的知识;尤其是数据库系统中优化器和执行器的具体实现方式。所以我们如下抽丝剥茧,逐一深入讨论问题。

1 子查询出现的位置,可以是: 目标列 / FROM子句中 / WHERE子句中....

    更多详情参见: 《数据库查询优化器的艺术》一书P18,2.2.1节

2 对于“子查询出现在目标列”,《数据库查询优化器的艺术》P18指出,“子查询如果位于目标列,则只能是标量子查询”。这点很重要。

3 举个例子mysql> create table t(a INT, b INT);
Query OK, 0 rows affected (0.11 sec)
mysql> select  from t;  ERROR 1241 (21000): Operand should contain 1 column(s)

说明:蓝色字体部分,是子查询,但是,MySQL报错信息说:只能包含一个列对象(注意这个表,我们定义了a和b两个列)

改造SQL如下,把子查询中的星号,变为聚集函数count(),此函数的返回之是一个单一的数值mysql> select (select from t) from t;
Empty set (0.00 sec)
说明:这样的语句,MySQL就没有报告错误。
接下来,我们再插入一些值。先插入第一行,然后执行在a列上的子查询。
mysql> insert into t values (1,1);
Query OK, 1 row affected (0.00 sec)
mysql> select (select  from t) from t;  +-------------------+
| (select a from t) |
+-------------------+
|                 1 |
+-------------------+
1 row in set (0.00 sec)
说明:子查询只有一个行,可以返回正确结果。
插入第2行,然后执行在a列上的与前面相同的子查询。
mysql> insert into t values (2,2);
Query OK, 1 row affected (0.01 sec)
mysql> select (select a from t) from t;

ERROR 1242 (21000): Subquery returns more than 1 row
说明:这回,我们看到,MySQL在数据多于一行的时候,报错,提示:子查询返回的值多于了一行
也就是说,目标列中的子查询返回值,;结合前面的示例,即有结论:。这其实就是说:目标列中的子查询只能是标量子查询。
 
4 什么是标量子查询:
   SQL允许子查询出现在返回单个值的表达式能够出现的任何地方,只要该子查询只返回包含单个属性的单个元组;这样的子查询称为标量子查询(scalar subquery)。
 
5 执行器对于目标列为子查询的执行:
继续使用上述示例,执行如下命令(此时表里已经有2条元组):mysql> select (select a from t as t2) from t as t1;
ERROR 1242 (21000): Subquery returns more than 1 row
这个错误,我们在之前已经看到过,不再多述。现就这个查询,进行分析。先看其查询执行计划。
mysql> explain select (select a from t as t2) from t as t1;
执行计划告诉我们,对于t2而言,目标列中的子查询,没有被优化,其select_type的值是SUBQUERY。
对于这种情况,执行器执行的时候,是用2层循环执行的,基本算法就是嵌套循环连接。大概方式为:
for (each row of t1)
{
    get r1 form t1;
    for (each row of t2)
    {
        get r2 from t2;
        join r1 and r2;
    }
}
这就是本文开始的问题所在,可能会使得大家会认为:我的主sql返回1000条数据,那么目标列处的子查询会被执行1000次
如果子查询在目标列,MySQL的执行器,就是这么执行的。所以,可能会使得“目标列处的子查询会被执行1000次”。但是,不要忘记另外一个条件,这个条件就是“”,所以,MySQL的执行器的真实实现方式如下,注意多出的蓝色字体部分:
for (each row of t1)
{
    get r1 form t1;  
    for (each row of t2)
    {
        get r2 from t2;
       
        join r1 and r2;     } }
所以,最终的结果是:我的主sql返回1000条数据,那么目标列处的子查询被执行1000次
6 一些优化方式---
mysql> select (select count(*) from t) from t;
mysql> explain select (select count(*) from t as t2) from t as t1;
2 rows in set, 1 warning (0.00 sec)

这个例子,就是一个优化方式。MySQL官方手册对 Select tables optimized away 的解释如下:

The query contained only aggregate functions (MIN(), MAX()) that were all resolved using an index, or COUNT(*) for MyISAM, and no GROUP BY clause. The optimizer determined that only one row should be returned.

其中, 特别需要注意的是:

1)COUNT(*) for MyISAM不正确,应该是对于InnoDB表也可以被优化。

2)这个优化,是在优化器的阶段进行的,得到的COUNT(*) 被缓存,然后在执行器阶段,不用再求值,也就不会有“目标列处的子查询会被执行1000次”的可能了

 

7 更进一步认识问题

是不是我们解释到这里就可以结束了?答案是:不是的。且看下例:

这个示例,子查询已经符合了标量子查询的要求(避开了第5条中所说的问题),t1中有2个元组,每一个元组,就可能致使子查询都被执行一次。mysql> select (select max(a) from t as t2) from t as t1;
+------------------------------+
| (select max(a) from t as t2) |
+------------------------------+
|                            2 |
|                            2 |
+------------------------------+
问题就这样又来了:
待续... 参见: http://blog.163.com/li_hx/blog/static/1839914132015528853902/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐