文档章节

mysql 子查询优化一例

Linland
 Linland
发布于 2015/04/28 10:34
字数 1273
阅读 206
收藏 18
写在前面的话:
  1. 在慢查优化12里都反复强调过 explain 的重要性,但有时候肉眼看不出 explain 结果如何指导优化,这时候还需要有一些其他基础知识的佐助,甚至需要了解 MySQL 实现原理,如子查询慢查优化
  2. 看到 SQL 执行计划中 select_type 字段中出现“DEPENDENT SUBQUERY”时,要打起精神了!

——MySQL 的子查询为什么有时候很糟糕——

引子:这样的子查询为什么这么慢?

下面的例子是一个慢查,线上执行时间相当夸张。为什么呢?

SELECT gid,COUNT(id) as count 

FROM shop_goods g1

WHERE status =0 and gid IN ( 

SELECT gid FROM shop_goods g2 WHERE sid IN  (1519066,1466114,1466110,1466102,1466071,1453929)

)

GROUP BY gid;

它的执行计划如下,请注意看关键词“DEPENDENT SUBQUERY”:

    id  select_type         table   type            possible_keys                           key           key_len  ref       rows  Extra      
------  ------------------  ------  --------------  --------------------------------------  ------------  -------  ------  ------  -----------
     1  PRIMARY             g1      index           (NULL)                                  idx_gid  5        (NULL)  850672 Using where
     2  DEPENDENT SUBQUERY  g2      index_subquery  id_shop_goods,idx_sid,idx_gid  idx_gid  5        func         1  Using where

 

基础知识:Dependent Subquery意味着什么

官方含义为:

SUBQUERY:子查询中的第一个SELECT;

DEPENDENT SUBQUERY:子查询中的第一个SELECT,取决于外面的查询 。

换句话说,就是 子查询对 g2 的查询方式依赖于外层 g1 的查询

什么意思呢?它意味着两步:

第一步,MySQL 根据 select gid,count(id) from shop_goods where status=0 group by gid; 得到一个大结果集 t1,其数据量就是上图中的 rows=850672 了。

第二步,上面的大结果集 t1 中的每一条记录,都将与子查询 SQL 组成新的查询语句:select gid from shop_goods where sid in (15...blabla..29) and gid=%t1.gid%。等于说,子查询要执行85万次……即使这两步查询都用到了索引,但不慢才怪。

如此一来,子查询的执行效率居然受制于外层查询的记录数,那还不如拆成两个独立查询顺序执行呢

 

优化策略1:

你不想拆成两个独立查询的话,也可以与临时表联表查询,如下所示:

SELECT g1.gid,count(1)

FROM shop_goods g1,(select gid from shop_goods WHERE sid in (1519066,1466114,1466110,1466102,1466071,1453929)) g2

where g1.status=0 and g1.gid=g2.gid

GROUP BY g1.gid;

也能得到同样的结果,且是毫秒级。

它的执行计划为:

    id  select_type  table           type    possible_keys              key            key_len  ref            rows  Extra                          
------  -----------  --------------  ------  -------------------------  -------------  -------  -----------  ------  -------------------------------
     1  PRIMARY      <derived2>      ALL     (NULL)                     (NULL)         (NULL)   (NULL)           30  Using temporary; Using filesort
     1  PRIMARY      g1              ref     idx_gid               idx_gid   5        g2.gid       1  Using where                    
     2  DERIVED      shop_goods  range   id_shop_goods,idx_sid  id_shop_goods  5        (NULL)           30  Using where; Using index      

DERIVED 的官方含义为:

DERIVED:用于 from 子句里有子查询的情况。MySQL 会递归执行这些子查询,把结果放在临时表里。

 

DBA观点引用:MySQL 子查询的弱点

hidba 论述道(参考资源3):

mysql 在处理子查询时,会改写子查询。

通常情况下,我们希望由内到外,先完成子查询的结果,然后再用子查询来驱动外查询的表,完成查询。

例如:

select * from test where tid in(select fk_tid from sub_test where gid=10)

通常我们会感性地认为该 sql 的执行顺序是:

sub_test 表中根据 gid 取得 fk_tid(2,3,4,5,6)记录,

然后再到 test 中,带入 tid=2,3,4,5,6,取得查询数据。

但是实际mysql的处理方式为:

select * from test where exists (

select * from sub_test where gid=10 and sub_test.fk_tid=test.tid

)

mysql 将会扫描 test 中所有数据,每条数据都将会传到子查询中与 sub_test 关联,子查询不会先被执行,所以如果 test 表很大的话,那么性能上将会出现问题。

 

《高性能MySQL》一书的观点引用

《高性能MySQL》的第4.4节“MySQL查询优化器的限制(Limitations of the MySQL Query Optimizer)”之第4.4.1小节“关联子查询(Correlated Subqueries)”也有类似的论述:

MySQL有时优化子查询很糟,特别是在WHERE从句中的IN()子查询。……

比如在sakila数据库sakila.film表中找出所有的film,这些film的actoress包括Penelope Guiness(actor_id = 1)。可以这样写:

mysql> SELECT * FROM sakila.film

-> WHERE film_id IN(

-> SELECT film_id FROM sakila.film_actor WHERE actor_id = 1);

mysql> EXPLAIN SELECT * FROM sakila.film ...;

+----+--------------------+------------+--------+------------------------+

| id | select_type        | table      | type   | possible_keys          |

+----+--------------------+------------+--------+------------------------+

| 1  | PRIMARY            | film       | ALL    | NULL                   |

| 2  | DEPENDENT SUBQUERY | film_actor | eq_ref | PRIMARY,idx_fk_film_id |

+----+--------------------+------------+--------+------------------------+

根据EXPLAIN的输出,MySQL将全表扫描film表,对找到的每行执行子查询,这是很不好的性能。幸运的是,很容易改写为一个join查询:

mysql> SELECT film.* FROM sakila.film

-> INNER JOIN sakila.film_actor USING(film_id)

-> WHERE actor_id = 1;

另外一个方法是通过使用GROUP_CONCAT()执行子查询作为一个单独的查询,手工产生IN()列表。有时候比join还快。(注:你不妨在我们的库上试试看 SELECT goods_id,GROUP_CONCAT(cast(id as char))

FROM bee_shop_goods

WHERE shop_id IN (1519066,1466114,1466110,1466102,1466071,1453929)

GROUP BY goods_id;)

MySQL已经因为这种特定类型的子查询执行计划而被批评。

 

何时子查询是好的

MySQL并不总是把子查询优化得很糟。有时候还是很优化的。下面是个例子:

mysql> EXPLAIN SELECT film_id, language_id FROM sakila.film

-> WHERE NOT EXISTS(

-> SELECT * FROM sakila.film_actor

-> WHERE film_actor.film_id = film.film_id

-> )G

……(注:具体文字还是请阅读《高性能MySQL》吧)

是的,子查询并不是总是被优化得很糟糕,具体问题具体分析,但别忘了 explain 。

本文转载自:http://www.cnblogs.com/zhengyun_ustc/archive/2013/11/29/slowquery3.html

共有 人打赏支持
Linland
粉丝 31
博文 51
码字总数 15762
作品 0
西安
数据库管理员
私信 提问
一例 Hive join 优化实战

由于 hive 与传统关系型数据库面对的业务场景及底层技术架构都有着很大差异,因此,传统数据库领域的一些技能放到 Hive 中可能已不再适用。关于 hive 的优化与原理、应用的文章,前面也陆陆续...

大数据之路
2014/08/29
0
2
浅谈 MySQL 子查询及其优化

使用过oracle或者其他关系数据库的DBA或者开发人员都有这样的经验,在子查询上都认为数据库已经做过优化,能够很好的选择驱动表执行,然后在把该经验移植到mysql数据库上,但是不幸的是,mys...

大数据之路
2014/07/09
0
1
[慢查优化]慎用MySQL子查询,尤其是看到DEPENDENT SUBQUERY标记时

写在前面的话: ——MySQL 的子查询为什么有时候很糟糕—— 引子:这样的子查询为什么这么慢? 下面的例子是一个慢查,线上执行时间相当夸张。为什么呢? SELECT gid,COUNT(id) as count FR...

旁观者-郑昀
2013/11/29
0
1
MySQL 子查询及其优化

使用过oracle或者其他关系数据库的DBA或者开发人员都有这样的经验,在子查询上都认为数据库已经做过优化,能够很好的选择驱动表执行,然后在把该经验移植到mysql数据库上,但是不幸的是,mys...

architect刘源源
03/08
0
0
解读MySQL 5.6 查询优化器新功能

【IT168 技术】MySQL 5.6中,Oracle对子查询进行了优化处理。其中涉及到将一个子查询转变为一个半连接操作,然后将它作为另外一个连接操作来加以评估。优化器能够进行识别,即一条IN语句要求...

TechTarget中国
2013/05/06
0
0

没有更多内容

加载失败,请刷新页面

加载更多

快速开发android,离不开这10个优秀的开源项目

作为一名菜鸡Android,时常瞻仰大佬们的开源项目是非常必要的。这里我为大家收集整理了10个优秀的开源项目,方便我们日常开发中学习! 作者:ListenToCode 博客:https://www.jianshu.com/p...

终端研发部
13分钟前
0
0
MiniUi tab页

architect刘源源
14分钟前
0
0
uitextview中'\n'不能换行的问题

后端返回的类似于这样的东西:'1、Fix bug;\n2、体验优化' 在textview里边是无法换行的,打断点发现 是因为 '\n'被转义成了'\\n'了 思路是replace。。。 _infoTextView.text = [self.info st...

RainOrz
19分钟前
0
0
年底了,该给自己写个总结了,一个六年女Java程序员的心声

简单的先说一下,坐标杭州,12届本科毕业,算上年前在阿里巴巴B2B事业部的面试,一共有面试了有6家公司(因为不想请假,因此只是每个晚上去其他公司面试,所以面试的公司比较少) 其中成功的...

Java架构资源分享
22分钟前
2
0
Shell脚本——cat/EOF输出多行

在某些场合,可能我们需要在脚本中生成一个临时文件,然后把该文件作为最终文件放入目录中。(可参考ntop.spec文件)这样有几个好处,其中之一就是临时文件不是唯一的,可以通过变量赋值,也...

吴伟祥
23分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部