Mysql 子查询效率优化

您所在的位置:网站首页 mysql查询一列和查询多列效率不一样吗怎么办 Mysql 子查询效率优化

Mysql 子查询效率优化

2024-07-14 07:08| 来源: 网络整理| 查看: 265

一、MySQL子查询的位置

       当一个查询是另一个查询的子部分是,称之为子查询(查询语句中嵌套含有查询语句)。子查询也是使用频率比较高的一种查询类型。因此,优化子查询,对于整个系统的性能也有直接的影响。

       从查询出现在SQL语句的位置来看,它可以出现在目标列中,也可以出现在from子句中,还可以出现在JOIN/ON子句、GROUPBY子句、HAVING子句、ORDERBY子句等位置。下面依次来看这几种形式的子查询,以及对他们进行优化的一些想法。

 

1、子查询出现在目标列位置

       当子查询出现在目标列位置的时候,这种查询只能是标量子查询。也就是说子查询返回的结果只能是一个元组的一个属性。否则,数据库会返回错误信息。

       下面为了实验上面这段话,我们来新建一些表,并插入一些数据。

1 2 3 create  table  t1 (k1  int  primary  key , c1  int ); create  table  t2 (k2  int  primary  key , c2  int ); insert  into  t2  values  (1, 10), (2, 2), (3,30);

a、此时若我们执行如下SQL语句的结果为:

1 2 mysql>  select  t1.c1, ( select  t2.c2  from  t2)  from  t1, t2; Empty  set  (0.00sec)

b、然后,我们往t1表中插入一些数据:

1 2 mysql>  insert  into  t1  values  (1, 1), (2, 2), (3, 3); Query OK, 3  rows  affected (0.00 sec)

c、此时,我们再次执行a中的查询,我们可以看到执行的结果        

1 2 mysql> select  t1.c1, ( select  t2.c2  from  t2)  from  t1, t2; ERROR 1242(21000): Subquery  returns  more than 1 row

d、此时我们清空t2表,然后再执行a中所做的查询。

1 2 3 4 5 mysql> delete  from  t2; QueryOK, 3  rows  affected (0.00 sec)    mysql>  select  t1.c1, ( select  t2.c2  from  t2)  from  t1, t2; Empty  set  (0.00 sec)

   此时返回的结果就又正常了。

e、我们进一步实验。现在我们把刚刚从t2表中删除的数据在插入到t2表:

1 2 mysql> insert  into  t2  values  (1, 10), (2, 2), (3, 30); Query OK,3  rows  affected (0.00 sec)

然后执行如下查询:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 mysql>  select  t1.c1, ( select  t2.c2  from  t2  where  k2=1)  from  t1, t2; + ------+-----------------------------------+ | c1   | ( select  t2.c2  from  t2  where  k2=1) | + ------+-----------------------------------+ |    1 |                                10 | |    2 |                                10 | |    3 |                                10 | |    1 |                                10 | |    2 |                                10 | |    3 |                                10 | |    1 |                                10 | |    2 |                                10 | |    3 |                                10 | + ------+-----------------------------------+

我们可以清楚的看到MySQL为我们返回的结果。

f、我们对e中的查询再换一种写法,可以看到返回的结果为

1 2 mysql>  select  t1.c1, (selectt2.c2  from  t2  where  c2 > 1)  from  t1, t2; ERROR 1242 (21000): Subqueryreturns more than 1 row

通过以上实验,我们可以得出这样一个结论:了子查询必须只能返回一个元组中的一个属性。或者,更严谨的说,出现在目标列上的子查询只能返回标量,即空值或单个元组的单个属性。

2、子查询出现在FROM字句的位置

       简单来说,FROM子句部分的子查询只能是非相关子查询,非相关子查询出现在FROM子句中可以上拉到父层,在多表连接时统一考虑连接代价然后进行优化。

       如果是相关子查询出现在FROM字句中,数据库可能返回错误提示。

接下来我们还是来看一些例子:

我们故意在FROM字句位置处使用相关子查询   

1 2 mysql>  select  *  from  t1, ( select  * from  t2  where  t1.k1 = t2.k2); ERROR 1248 (42000): Every derived  table  musthave its own alias

 

我们把相关条件去掉后可以得出:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 mysql>  select  *  from  t1, ( select  *  from  t2)  as  a_t2; + ----+------+----+------+ | k1 | c1   | k2 | c2   | + ----+------+----+------+ |  1 |    1 |  1 |   10 | |  2 |    2 |  1 |   10 | |  3 |    3 |  1 |   10 | |  1 |    1 |  2 |    2 | |  2 |    2 |  2 |    2 | |  3 |    3 |  2 |    2 | |  1 |    1 |  3 |   30 | |  2 |    2 |  3 |   30 | |  3 |    3 |  3 |   30 | + ----+------+----+------+ 9  rows  in  set  (0.00 sec)

3、子查询出现在WHERE子句当中

       出现在WHERE子句中的子查询,是一个条件表达式的一部分,而表达式可以分为操作符和操作数;根据参与运算的操作符的不同类型,操作符也不尽相同。如INT型有>,  select  *  from  t1  where  c1 =  ANY  ( select  c2  from  t2  where  t2.c2 = t1.c1); + ----+------+ | k1 | c1   | + ----+------+ |  2 |    2 | + ----+------+ 1 row  in  set  (0.12 sec)

       非相关子查询:子查询的执行不依赖与外层父查询的任何属性。这样的子查询具有独立性,可以独自求解,形成的一个子查询计划先与外层的查询求解。下面给出一个例子:

1 2 mysql>  select  *  from  t1  where  c1 =  ANY ( select  c2  from  t2  where  t2.c2=10); Empty  set  (0.02 sec)

2、从特定的谓词来区分

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

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

 

(PS:子查询的分类还可以从语句的构成的复杂程度和查询的结果等方面来进行分类,这里不再赘述,我们把重点放在如何对子查询进行优化上) 

三、如何对子查询进行优化

1、子查询合并

       在某些情况下,多个子查询可以合并为一个子查询。合并的条件是语义等价,即合并前后的查询产生相同的结果集。合并后还是子查询,可以通过其他技术消除子查询。这样可以把多次表扫描,多次表连接转化为单次表扫描和单次表连接,例如:

1 2 3 4 5 6 7 8 9 10 11 12 mysql>  select  *  from  t1  where  k1  exists( select  k2  from  t2  where  t2.k2  exists( select  k2  from  t2  where  t2.k2  ); + ----+------+ | k1 | c1   | + ----+------+ |  1 |    1 | |  2 |    2 | |  3 |    3 | + ----+------+ 3  rows  in  set  (0.12 sec)

我们可以查看这条语句的查询执行计划:

1 2 3 4 5 6 7 8 9 10 11 12 mysql> explain extended  select  *  from  t1  where  k1  exists( select  k2  from  t2  where  t2.k2  exists( select  k2  from  t2  where  t2.k2  ); + ----+-------------+-------+-------+---------------+---------+---------+------+------+----------+-------------+ | id | select_type |  table  | type  | possible_keys |  key      | key_len | ref  |  rows  | filtered | Extra       | + ----+-------------+-------+-------+---------------+---------+---------+------+------+----------+-------------+ |  1 |  PRIMARY      | t1    | range |  PRIMARY        |  PRIMARY  | 4       |  NULL  |    3 |   100.00 | Using  where  | |  3 | SUBQUERY    | t2    | range |  PRIMARY        |  PRIMARY  | 4       |  NULL  |    3 |   100.00 | Using  where  | |  2 | SUBQUERY    | t2    | range |  PRIMARY        |  PRIMARY  | 4       |  NULL  |    3 |   100.00 | Using  where  | + ----+-------------+-------+-------+---------------+---------+---------+------+------+----------+-------------+ 3  rows  in  set , 1 warning (0.00 sec)

可以看到,这条查询语句有两个子查询。

 

我们把这条语句化简:

1 2 3 4 5 6 7 8 9 10 11 mysql>  select  *  from  t1  where  k1  exists( select  k2  from  t2  where  t2.k2  ); + ----+------+ | k1 | c1   | + ----+------+ |  1 |    1 | |  2 |    2 | |  3 |    3 | + ----+------+ 3  rows  in  set  (0.00 sec)

我们再来查看这一条语句的查询执行计划:

1 2 3 4 5 6 7 8 9 10 mysql> explain extended  select  *  from  t1  where  k1  exists( select  k2  from  t2  where  t2.k2  ); + ----+-------------+-------+-------+---------------+---------+---------+------+------+----------+-------------+ | id | select_type |  table  | type  | possible_keys |  key      | key_len | ref  |  rows  | filtered | Extra       | + ----+-------------+-------+-------+---------------+---------+---------+------+------+----------+-------------+ |  1 |  PRIMARY      | t1    | range |  PRIMARY        |  PRIMARY  | 4       |  NULL  |    3 |   100.00 | Using  where  | |  2 | SUBQUERY    | t2    | range |  PRIMARY        |  PRIMARY  | 4       |  NULL  |    3 |   100.00 | Using  where  | + ----+-------------+-------+-------+---------------+---------+---------+------+------+----------+-------------+ 2  rows  in  set , 1 warning (0.00 sec)

很明显,我们已经消除了一套子查询,但是最后结果是一样的。

 

两个EXISTS子句可以合并为一个,条件也进行了合并。

2、子查询展开

       又称为子查询的反嵌套或者是子查询的上拉。把一些子查询置于外层的父查询中,其实质是把某些子查询转化为等价的多表连接操作。带来的一个明显的好处就是,有关访问路径,连接方法和连接顺序可能被有效的利用,使得查询语句的层次尽可能的减少。

       常见的IN、SOME、ALL、EXISTS依据情况转换为半连接(SEMI JOIN)、普通类型的子查询等情况属于此类。我们直接比较两条语句的查询执行计划:

1 2 3 4 5 6 7 8 9 mysql> explain  select  *  from  t1, ( select  *  from  t2  where  t2.k2 > 10) v_t2  where  t1.k1  EXPLAIN EXTENDED  SELECT  *  FROM  t1  WHERE  t1.a1>( SELECT  MIN (t2.a2)  FROM  t2); + ----+-------------+-------+-------+---------------+---------+---------+------+------+----------+------------------------------+ | id | select_type |  table  | type  | possible_keys |  key      | key_len | ref  |  rows  | filtered | Extra                        | + ----+-------------+-------+-------+---------------+---------+---------+------+------+----------+------------------------------+ |  1 |  PRIMARY      | t1    | range |  PRIMARY        |  PRIMARY  | 4       |  NULL  | 7534 |   100.00 | Using  where                   | |  2 | SUBQUERY    |  NULL   |  NULL   |  NULL           |  NULL     |  NULL     |  NULL  |  NULL  |      NULL  |  Select  tables optimized away | + ----+-------------+-------+-------+---------------+---------+---------+------+------+----------+------------------------------+ 2  rows  in  set , 1 warning (0.11 sec)

查询执行的结果依然含有子查询,所以MySQL不支持对这种查询进行优化

2、MySQL查询优化实例

MySQL对NOT IN类型的子查询进行优化

1 2 3 4 5 6 7 8 mysql> explain  extended  select  *  from  t1  where  t1.a1  NOT  IN  ( select  a2  from  t2  where  t2.a2 > 10); + ----+-------------+-------+-------+---------------+---------+---------+------+-------+----------+--------------------------+ | id | select_type |  table  | type  | possible_keys |  key      | key_len | ref  |  rows   | filtered | Extra                    | + ----+-------------+-------+-------+---------------+---------+---------+------+-------+----------+--------------------------+ |  1 |  PRIMARY      | t1    |  ALL    |  NULL           |  NULL     |  NULL     |  NULL  | 15068 |   100.00 | Using  where               | |  2 | SUBQUERY    | t2    | range |  PRIMARY        |  PRIMARY  | 4       |  NULL  |  7534 |   100.00 | Using  where ; Using  index  | + ----+-------------+-------+-------+---------------+---------+---------+------+-------+----------+--------------------------+ 2  rows  in  set , 1 warning (0.00 sec)

通过反编译查询语句我们可以发现,虽然子查询没有被消除,但是NOT IN子查询被物化,达到了部分优化的结果。

1 2 3 4 5 6 mysql> show warnings\G *************************** 1. row ***************************    Level : Note     Code: 1003 Message: /*  select #1 */  select  `testdb`.`t1`.`a1`  AS  `a1`,`testdb`.`t1`.`b1`  AS  `b1`  from  `testdb`.`t1`  where  ( not ((`testdb`.`t1`.`a1`,`testdb`.`t1`.`a1`  in  (  (/*  select #2 */  select  `testdb`.`t2`.`a2`  from  `testdb`.`t2`  where  (`testdb`.`t2`.`a2` > 10) ), (`testdb`.`t1`.`a1`  in    on    where  ((`testdb`.`t1`.`a1` = `materialized-subquery`.`a2`))))))) 1 row  in  set  (0.00 sec)

MySQL对ALL类型的子查询进行优化:

1 2 3 4 5 6 7 8 mysql> explain extended  select  *  from  t1  where  t1.a1 >  ALL ( select  a2  from  t2  where  t2.a2 > 10); + ----+-------------+-------+------+---------------+------+---------+------+-------+----------+------------------------------+ | id | select_type |  table  | type | possible_keys |  key   | key_len | ref  |  rows   | filtered | Extra                        | + ----+-------------+-------+------+---------------+------+---------+------+-------+----------+------------------------------+ |  1 |  PRIMARY      | t1    |  ALL   |  NULL           |  NULL  |  NULL     |  NULL  | 15068 |   100.00 | Using  where                   | |  2 | SUBQUERY    |  NULL   |  NULL  |  NULL           |  NULL  |  NULL     |  NULL  |   NULL  |      NULL  |  Select  tables optimized away | + ----+-------------+-------+------+---------------+------+---------+------+-------+----------+------------------------------+ 2  rows  in  set , 1 warning (0.03 sec)

反编译可以看到ALL被优化为>MAX的操作。

1 2 3 4 5 6 mysql> show warnings\G *************************** 1. row ***************************    Level : Note     Code: 1003 Message: /*  select #1 */  select  `testdb`.`t1`.`a1`  AS  `a1`,`testdb`.`t1`.`b1`  AS  `b1`  from  `testdb`.`t1`  where  ((`testdb`.`t1`.`a1`  10)))) 1 row  in  set  (0.00 sec)

MySQL对SOME类型的子查询进行优化 

1 2 3 4 5 6 7 8 mysql> explain extended  select  *  from  t1  where  t1.a1 >  SOME  ( select  a2  from  t2  where  t2.a2 > 10); + ----+-------------+-------+------+---------------+------+---------+------+-------+----------+------------------------------+ | id | select_type |  table  | type | possible_keys |  key   | key_len | ref  |  rows   | filtered | Extra                        | + ----+-------------+-------+------+---------------+------+---------+------+-------+----------+------------------------------+ |  1 |  PRIMARY      | t1    |  ALL   |  NULL           |  NULL  |  NULL     |  NULL  | 15068 |   100.00 | Using  where                   | |  2 | SUBQUERY    |  NULL   |  NULL  |  NULL           |  NULL  |  NULL     |  NULL  |   NULL  |      NULL  |  Select  tables optimized away | + ----+-------------+-------+------+---------------+------+---------+------+-------+----------+------------------------------+ 2  rows  in  set , 1 warning (0.00 sec)

可以看到对SOME类型的操作转化为对MIN类型的操作

1 2 3 4 5 6 mysql> show warnings\G *************************** 1. row ***************************    Level : Note     Code: 1003 Message: /*  select #1 */  select  `testdb`.`t1`.`a1`  AS  `a1`,`testdb`.`t1`.`b1`  AS  `b1`  from  `testdb`.`t1`  where  ((`testdb`.`t1`.`a1` > (/*  select #2 */  select  min (`testdb`.`t2`.`a2`)  from  `testdb`.`t2`  where  (`testdb`.`t2`.`a2` > 10)))) 1 row  in  set  (0.00 sec)

对ANY类型的优化和对SOME类型的优化等同

 

附:explain的用法

explain语句用于查看一条SQL语句的查询执行计划,用法很简单,直接把explain放到要执行的SQL语句的前面即可。explain extended和explain的输出结果一样,只是用explain extended语句后可以通过show warnings查看一条SQL语句的反编译的结果,让我们知道我们输入的一条SQL语句真正是怎么执行的。

 

对输入结果简单解释一下:

select_type:表示select类型,常见的取值有SIMPLE(不使用表连接或子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION中的或者后面的查询语句)、SUBQUERY(子查询中的第一个select)等。

table:输出结果集的表。

type:表示表的连接类型,性能由好到差的连接类型为system(表中仅有一行,即常量表)、const(单表中最多有一个匹配行,例如PRIMARY KEY或者UNIQUE INDEX)、eq_ref(对于前面的每一行,在此表中只查询一条记录,简单来说,就是多表连接中使用PRIMARYKEY或者UNIQUE INDEX)、ref(与eq_ref类似,区别在于不使用PRIMARYKEY或者UNIQUE INDEX,而是使用普通的索引)、ref_of_null(与ref类似,区别在于条件中包含对NULL的查询)、index_merge(索引合并化)、unique_subquery(in的后面是一个查询主键字段的子查询)、index_subquery(与unique_subquery类似,区别在于in的后面是查询非唯一索引字段的子查询)、range(单表中的范围查询)、index(对于前面的每一行都通过查询索引来得到数据)、all(对于前面的每一行的都通过全表扫描来获得数据)。

possible_keys:表示查询时,可能使用到的索引。

key:表示实际使用的索引

key_len:索引字段的长度

rows:扫描行的数量

extra:执行情况的说明和描述。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3