MySQL的万能"嵌套循环"并不是对每种查询都是最优的。不过还好,mysql查询优化器只对
少部分查询不适用,而且我们往往可以通过改写查询让mysql高效的完成工作。
在这我们先来看看mysql优化器有哪些局限性:

1.关联子查询

mysql的子查询实现得非常糟糕。最糟糕得一类查询是where条件中包含in()的子查询语句。
例如,我们希望找到sakila数据库中,演员Penlope Guiness参演的所有影片信息。
很自然的,我们会按照下面的方式用子查询实现:

   select * from sakila.film
  where film_id in (
    select film_id from sakila.film_actor where actor_id = 1
  )

你很容易认为mysql应该由内而外的去执行这个查询,通过子查询中的条件先找出所匹配的
film_id。所以你看你会认为这个查询可能会是这样:

-- SELECT GROUP_CONCAT(film_id) FROM sakila.film_actor WHERE actor_id = 1;
-- Result: 1,23,25,106,140,166,277,361,438,499,506,509,605,635,749,832,939,970,980
SELECT * FROM sakila.film
WHERE film_id
IN(1,23,25,106,140,166,277,361,438,499,506,509,605,635,749,832,939,970,980);

不幸的是,事实恰恰相反。MYSQL想通过外部的关联条件用来快速的筛选子查询,它可能认为
这会让子查询更效率。mysql会这样重写查询:

SELECT * FROM sakila.film
WHERE EXISTS (
SELECT * FROM sakila.film_actor WHERE actor_id = 1
AND film_actor.film_id = film.film_id);

这样的话,子查询将会依赖外部表的数据,而不会被优先执行。
mysql将会全表扫描film表,然后循环执行子查询。在外表很小的情况下,
不会有什么问题,但在外表很大的情况下,性能将会非常差。幸运的是,
很容易用关联查询来重写。

mysql> SELECT film.* FROM sakila.film
  -> INNER JOIN sakila.film_actor USING(film_id)
  -> WHERE actor_id = 1;

其他的好的优化方法是用group_concat手工生成in()的列表。有时甚至会比JOIN查询
更快。总之,虽然in()子查询在很多情况下工作不佳,但exist()或者其他等价的子查询
有时也工作的不错。

关联子查询性能并不是一直都很差的。

子查询 VS 关联查询

--关联子查询
mysql> explain select film_id, language_id from sakila.film
    where not exsits (
      select * from sakila.film_actor
      where film_actor.film_id = film.film_id
    )

********************* 1. row ***********************************
id : 1
select_type: PRIMARY
table: film
type: all
possible_keys: null
key: null
key_len: null
ref: null
rows: 951
Extra: Using where

********************* 2. row ***********************************
id : 2
select_type: Dependent subquery
table: film_actor
type: ref
possible_keys: idx_fx_film_id
key: idx_fx_film_id
key_len: 2
ref: film.film_id
rows: 2
Extra: Using where;Using index

--关联查询
mysql> explain select film.film_id, film.language_id from sakila.film
    left outer join sakila.film_actor using(film_id)
    where film_actor.film_id is null

********************* 1. row ***********************************
id : 1
select_type: simple
table: film
type: all
possible_keys: null
key: null
key_len: null
ref: null
rows: 951
Extra:

********************* 2. row ***********************************
id : 1
select_type: simple
table: film_actor
type: ref
possible_keys: idx_fx_film_id
key: idx_fx_film_id
key_len: 2
ref: sakila.film.film_id
rows: 2
Extra: Using where;Using index;not exists;

可以看到,这里的执行计划几乎一样,下面是一些细微的差别:
1. 表 film_actor的访问类型一个是Dependent subquery 另一是simple,这对底层存储引擎接口来说,没有任何不同;

2. 对 film表 第二个查询没有using where,但这不重要。using子句和where子句实际上是完全一样的。

3. 第二个表film_actor的执行计划的Extra 有 "Not exists" 这是我们先前提到的提前终止算法,mysql通过not exits优化
来避免在表film_actor的索引中读取任何额外的行。这完全等效于直接使用 not exist ,这个在执行计划中也一样,一旦匹配到一行
数据,就立刻停止扫描

测试结果为:
查询 每秒查询数结果(QRS)
NOT EXISTS 子查询 360
LEFT OUTER JOIN 425
这里显示使用子查询会略慢些。

另一个例子:
不过每个具体地案例会各有不同,有时候子查询写法也会快些。例如,当返回结果只有一个表的某些列的时候。
听起来,这种情况对于关联查询效率也会很好。具体情况具体分析,例如下面的关联,我们希望返回所有包含同一个演员参演的电影
因为电影会有很多演员参演,所以可能返回一些重复的记录。

mysql-> select film.film_id from sakila.film
     inner join sakila.film_actor using (film_id)

我们需要用distinct 和 group by 来移除重复的记录

mysql-> select distinct film.film_id from sakila.film
    inner join sakila.film_actor using (film_id)

但是,回头看看这个查询,到底这个查询返回的结果意义是什么?至少这样的写法会让sql的意义不明显。
如果是有exists 则很容易表达"包含同一个参演演员"的逻辑。而且不需要使用 distinct 和 Group by,也不会有重复的结果集。
我们知道一旦使用了 distinct 和 group by 那么在查询的执行过程中,通常需要产生临时中间表。

mysql -> select film_id from sakila.film_actor
    where exists(select * from sakila.film_actor
    where film.film_id = film_actor.film_id)

测试结果为:
查询 每秒查询数结果(QRS)
INNER JOIN 185
EXISTS 子查询 325
这里显示使用子查询会略快些。

通过上面这个详细的案例,主要想说明两点: 
一是不需要听取哪些关于子查询的 "绝对真理",(即别用使用子查询)
二是应该用测试来验证子查询的执行疾患和响应时间的假设。

2.union的限制
有时,mysql无法将限制条件从外层"下推"到内层,这使得一些可以限制结果集和附加的优化都无法运行。

如果你想任何单独的查询都可以从一个limit获益,
或者你想order by也是基于所有子查询一次结合,
则你需要在每个子查询加上相应的子语句。

例如:

(SELECT first_name, last_name
  FROM sakila.actor
  ORDER BY last_name)
UNION ALL
(SELECT first_name, last_name
  FROM sakila.customer
  ORDER BY last_name)
LIMIT 20;

这个查询将会保存200行从actor查出来的数据和customer表的599行数据,

然后放入一个临时表,然后选取靠前的20条数据。

你可以通过在每个查询都加上limit 20 来预防这个情况。

如下:

 
(SELECT first_name, last_name
  FROM sakila.actor
  ORDER BY last_name
  LIMIT 20)
UNION ALL
(SELECT first_name, last_name
  FROM sakila.customer
  ORDER BY last_name
  LIMIT 20)
LIMIT 20;
 

这样只会查出40条数据了,大大提升了查询效率。

3.索引合并优化

  

4.等值传递

  有时候等值传递也会造成很大的性能消耗。

5.并行执行

  mysql不能并行执行一个单独的查询在不同的cpu.可能其他数据库会提供这个特性,但mysql没有提供。

  我们提及这个就是希望你们不要花时间去弄怎么在mysql配置并行查询。

6.索引关联

  mysql并不是完全支持哈希关联,大部分关联都是嵌套循环关联。

7.松散索引扫描

  由于历史原因,mysql不支持松散索引扫描

8.最大值和最小值优化

  对于min()和max(),mysql优化做的并不好

9.在同一个表查询和更新

  下面是个无法运行的sql,虽然这是一个符合标准的sql语句。

  这个sql语句尝试将两个表中相似行的数量记录到字段cnt中:

  

mysql> UPDATE tbl AS outer_tbl
  -> SET cnt = (
  ->   SELECT count(*) FROM tbl AS inner_tbl
  ->   WHERE inner_tbl.type = outer_tbl.type
  -> );

  ERROR 1093 (HY000): You can’t specify target table 'outer_tbl' for update in FROM
  clause

 

可以通过生成表的形式来绕过上面的限制,因为mysql只会把这个表当做一个临时表处理。实际上,

这执行了两个查询:一个是子查询的select语句,另一个是多表关联update,只是关联的表是一个临时表。

子查询会在update语句打开表之前就完成,所以下面的查询将正常运行。

 
mysql> UPDATE tbl
  -> INNER JOIN(
  ->   SELECT type, count(*) AS cnt
  ->   FROM tbl
  ->   GROUP BY type
  -> ) AS der USING(type)
  -> SET tbl.cnt = der.cnt;
 

010 --MySQL查询优化器的局限性的更多相关文章

  1. mysql查询优化之二:查询优化器的局限性

    在<mysql查询优化之一:mysql查询优化常用方式>一文中列出了一些优化器常用的优化手段.查询优化器在提供这些特性的同时,也存在一定的局限性,这些局限性往往会随着MySQL版本的升级而 ...

  2. Atitit Mysql查询优化器 存取类型 范围存取类型 索引存取类型 AND or的分析

    Atitit Mysql查询优化器 存取类型 范围存取类型 索引存取类型 AND or的分析     Atitit Mysql查询优化器 存取类型 范围存取类型 索引存取类型 AND or的分析1 存 ...

  3. 1025WHERE执行顺序以及MySQL查询优化器

    转自http://blog.csdn.net/zhanyan_x/article/details/25294539 -- WHERE执行顺序-- 过滤比较多的放在前面,然后更加容易匹配,从左到右进行执 ...

  4. mysql查询优化器为什么可能会选择错误的执行计划

    有可能导致mysql优化器选择错误的执行计划的原因如下: A:统计信息不准确,mysql依赖存储引擎为其提供的统计信息来评估成本,然而有的存储引擎提供的信息是准确的,有的引擎提供的可能就偏差很大,如: ...

  5. Mysql查询优化器

    Mysql查询优化器 本文的目的主要是通过告诉大家,查询优化器为我们做了那些工作,我们怎么做,才能使查询优化器对我们的sql进行优化,以及启示我们sql语句怎么写,才能更有效率.那么到底mysql到底 ...

  6. Mysql查询优化器浅析

    --Mysql查询优化器浅析 -----------------------------2014/06/11 1 定义    Mysql查询优化器的工作是为查询语句选择合适的执行路径.查询优化器的代码 ...

  7. 20170103简单解析MySQL查询优化器工作原理

    转自博客http://www.cnblogs.com/hellohell/p/5718238.html 感谢楼主的贡献 查询优化器的任务是发现执行SQL查询的最佳方案.大多数查询优化器,包括MySQL ...

  8. MySQL查询优化器工作原理解析

    手册上查询优化器概述 查询优化器的任务是发现执行SQL查询的最佳方案.大多数查询优化器,包括MySQL的查询优化器,总或多或少地在所有可能的查询评估方案中搜索最佳方案.对于联接查询,MySQL优化器所 ...

  9. mysql查询优化器的提示(hit)

    如果对优化器选择的执行计划不满意,可以使用优化器提供的几个提示来控制最终的执行计划,关于每个提示的具体用法,建议直接阅读官方手册,一些提示和版本有直接关系,可以使用的一些提示如下: high_prio ...

随机推荐

  1. VMWare虚拟机下为Ubuntu 12.04.1配置静态IP(NAT方式)

    背景 在虚拟机下运行操作系统,尤其是Linux系统已经是非常常见的做法.有时你想在虚拟机下搭建一个(模拟)服务器来供主机访问,比如搭建一个telnet/ssh.此时你会发现,每次启动虚拟机,VMWar ...

  2. 学习Android之SimpleAdapter显示网络图片

    效果图: 此程序基本的知识点是:SimpleAdapter本身是不支持网络图片的, 假设在Map.put(a,b)中 b为一个Bitmap,程序不会报红色字体,而是在控制台输出绿色的字体,例如以下 0 ...

  3. centos安装swoole

        编译安装swoole: cd && wget https://github.com/swoole/swoole-src/archive/1.8.6-stable.tar.gz  ...

  4. webpack.config.js配置遇到Error: Cannot find module '@babel/core'问题

    在webpack配置,将ES6转成ES5的时候,,出现Error: Cannot find module '@babel/core'错误最初以为是babel-core没有安装上.重装了好几遍babel ...

  5. kendo ui - MultiSelect 多选系列

    kendo-ui 官网:https://www.telerik.com/documentation 初始化 grid: 引入文件: <link rel="stylesheet" ...

  6. SERVICE问题解决方法

    这篇文章主要介绍了Windows服务器下出现ZendOptimizer.MemoryBase@NETWORK SERVICE问题解决方法,需要的朋友可以参考下 日志提示 事件 ID ( 2 )的描述( ...

  7. ZOJ 2017 Quoit Design 经典分治!!! 最近点对问题

    Quoit Design Time Limit: 5 Seconds      Memory Limit: 32768 KB Have you ever played quoit in a playg ...

  8. 配虚拟ip脚本

    cat /home/master/init_pandora.sh #! /bin/shuser=`whoami`if [ $user = 'master' ]then sudo /sbin/ifcon ...

  9. ASP.NET Core多语言 (转载)

    ASP.NET Core中提供了一些本地化服务和中间件,可将网站本地化为不同的语言文化.ASP.NET Core中我们可以使用Microsoft.AspNetCore.Localization库来实现 ...

  10. windows内核Api的学习

    windows内核api就是ntoskrnl.exe导出的函数.我们能够跟调用应用层的api一样,调用内核api. 只是内核api须要注意的是.假设函数导出了.而且函数文档化(也就是能够直接在msdn ...