在《mysql查询优化之一:mysql查询优化常用方式》一文中列出了一些优化器常用的优化手段。查询优化器在提供这些特性的同时,也存在一定的局限性,这些局限性往往会随着MySQL版本的升级而得到改善,所以本文会列出一些常见的局限性,且不包含所有的。

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.等值传递

  有时候等值传递也会造成很大的性能消耗。例如,有一个非常大的In()列表,而mysql优化器发现存在WHERE、ON或者USING的子句,将这个列表的值和另一个表的某个列关联。

那么优化器会将IN()列表都复制应用到关联的各个表中。通常因为各个表新增了过滤条件,优化器可以更高效地从存储引擎过滤记录。但是如果这个列表非常大,则会导致优化和执行都会变得很慢。

5.并行执行

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

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

6.哈希关联

  mysql并不是完全支持哈希关联,大部分关联都是嵌套循环关联。不过可以通过建立一个哈希索引来曲线地实现哈希关联。

7.松散索引扫描

  由于历史原因,mysql并不支持松散索引扫描(相当于oracle中的跳跃索引扫描),也就无法按照不连续的方式扫描一个索引。通常,mysql的索引扫描需要定义一个起点和一个重点,即使需要的数据只是这段索引中很少数的几个,mysql仍需要扫描这段索引中的每个条目。

8.最大值和最小值优化

  

对于min()和max()查询,mysql的优化做的并不好。有一个很明显的例子:
mysql> SELECT MIN(actor_id) FROM sakila.actor WHERE first_name='PENELOPE'
因为在first_name字段上没有索引,因此mysql将会进行一次全表扫描。如果mysql能够进行主键扫面,那么理论上,当mysql读到第一个满足条件的记录的时候就是我们需要找的最小值了,因为主键是严格感召actor_id字段的大小顺序排序的。但是mysql这时只会做全表扫描。一个曲线的优化方法是移除min(),然后使用limit来将查询重写如下:
mysql> SELECT actor_id FROM sakila.actor USE INDEX(PRIMARY)
-> WHERE first_name = 'PENELOPE' limit 1;
这个策略可以让mysql扫描尽可能少的数据记录。

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

  mysql不容许对同一表同时进行查询和更新。这其实并不是优化器的限制,如果清楚mysql是如何执行查询的,就可以避免这种情况。下面是一个无法运行的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;

mysql查询优化之二:查询优化器的局限性的更多相关文章

  1. mysql查询优化之三:查询优化器提示(hint)

    目录: <MySQL中的两种临时表>--强制使用临时表 SQL_BUFFER_RESULT <MySQL 多表关联更新及删除> <mysql查询优化之三:查询优化器提示( ...

  2. MySQL索引原理以及查询优化

    转载自:https://www.cnblogs.com/bypp/p/7755307.html MySQL索引原理以及查询优化 一.介绍 1.什么是索引? 一般的应用系统,读写比例在10:1左右,而且 ...

  3. mysql 索引原理及查询优化 -转载

    转载自 mysql 索引原理及查询优化 https://www.cnblogs.com/panfb/p/8043681.html 潘红伟   mysql 索引原理及查询优化 一 介绍 为何要有索引? ...

  4. MySQL性能优化(二):优化数据库的设计

    原文:MySQL性能优化(二):优化数据库的设计 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.n ...

  5. 部署MySQL主主复制管理器

    一.概念 MMM(Master-Master replication manager for MvSQL,MySQL主主复制管理器)是一套支持双主故障切换和双主日常管理的脚本程序.MMM 使用 Per ...

  6. MySQL 系列(二) 你不知道的数据库操作

    第一篇:MySQL 系列(一) 生产标准线上环境安装配置案例及棘手问题解决 第二篇:MySQL 系列(二) 你不知道的数据库操作 本章内容: 查看\创建\使用\删除 数据库 用户管理及授权实战 局域网 ...

  7. MySQL基础(二)——DDL语句

    MySQL基础(二)--DDL语句 1.什么是DDL语句,以及DDL语句的作用 DDL语句时操作数据库对象的语句,这些操作包括create.drop.alter(创建.删除.修改)数据库对象. 2.基 ...

  8. MySQL系列(二)---MySQL事务

    MySql 事务 目录 MySQL系列(一):基础知识大总结 MySQL系列(二):MySQL事务 什么是事务(transaction) 保证成批操作要么完全执行,要么完全不执行,维护数据的完整性.也 ...

  9. mysql 数据库(二)数据库的基本操作

    mysql 数据库(二)数据库的基本操作 用户管理,添加权限,创建,显示,使用数据库 1 显示数据库:show databases; 默认数据库: mysql - 用户权限相关数据 test - 用于 ...

  10. MySQL 系列(二)Jdbc

    MySQL 系列(二)Jdbc 一.Jdbc 基本操作 import java.sql.Connection; import java.sql.DriverManager; import java.s ...

随机推荐

  1. 07 Object类,Scanner,Arrays类,String类,StringBuffer类,包装类

    Object类的概述:* A:Object类概述    * 类层次结构的根类    * 所有类都直接或者间接的继承自该类* B:构造方法    * public Object()    * 子类的构造 ...

  2. EasyUI datagrid 数据加载

    网上找了好多人的方法发现都有问题发一个可用方便的 主要分三种情况 加载1,loaddata 加载2,datagrid 加载3, url 加载 第一部分,datagrid加载 第二部分,点击 datag ...

  3. 《DSP using MATLAB》Problem 6.9

    9月9日,我们怀念毛主席! 代码: %% +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ...

  4. 顺平讲try catch finally throw throws(精华)

    try catch finally  有点像if else语句 还有像javascript的服务器执行成功后的回调函数,success:function(){ 进行处理 }; throws的意思是将异 ...

  5. lua-resty-qless-web UI 界面运行

    lua-resty-qless-web 是 lua-resty-qless 的web 管理界面以及lua-resty-template 模版引擎开发的,里面实现了一个简单的 路由功能 备注: demo ...

  6. .NET本质论 方法

    方法和JIT编译 CLR只执行本机的机器代码.如果一个方法体由CIL组成,那么它就必须在调用之前被转换为本机的机器码(将MSIL编译为本机代码,运行库提供了两种方式.一种就是在安装与部署时的预编译(由 ...

  7. oracle-srvctl-output

    ############################## [grid@rac01 ~]$ crsctl query crs administratorCRS Administrator List: ...

  8. Python time & datetime模块

    time 模块 时间分为三种格式: 时间戳:表示1970年1月1日之后的秒 结构化时间:元组包含了:年.日.星期等... 格式化字符串:格式可以自定义 时间戳: import time time_st ...

  9. day 34 线程队列 线程池 协程 Greenlet \Gevent 模块

    1 线程的其他方法 threading.current_thread().getName()    查询当前线程对象的名字 threading.current_thread().ident      ...

  10. vsphere和vmware快照的不足之处

    当快照创建时虚拟机执行一个读操作,hypervisor会检查快照VMDK,查看是否有被读取的区块存在.如果有,则从快照中为虚拟机提供这个区块,如果没有,虚拟机还需要去读取基础VMDK.如果只有一个快照 ...