相关文章推荐
霸气的骆驼  ·  连接MySql报错Unknown ...·  2 周前    · 
唠叨的硬盘  ·  如何实现MySQL case when ...·  1 周前    · 
豪气的移动电源  ·  mysql ...·  4 天前    · 
纯真的饭卡  ·  hivesql ...·  1 年前    · 
刚毅的刺猬  ·  BlockingCollection<t> ...·  1 年前    · 

总的来说, mysql认为任何一个查询都是一次关联,并不仅仅是一个查询需要用到两个表匹配才叫关联,所以,在 mysql中,每一个查询,每一个片段(包括子查询,甚至单表 select)都可能是关联。所以,理解 mysql如何执行关联查询至关重要,先来看一个 union的例子,对于 union, mysql先将一系列的单个查询结果放到一个临时表中,然后再重新读出临时表数据来完成 union,在 mysql的概念中,每个查询都是一次关联,所以读取结果临时表也是一次关联。

当前 mysql关联执行的策略很简单, mysql对任何关联都执行嵌套循环关联操作,即, mysql先在一个表中循环读取单条数据,然后再嵌套循环到下一个表中寻找匹配的行,依次下去,直到找到所有表中匹配的行为止。然后根据各个表匹配的行,返回查询中需要的各个列, mysql会尝试在最后一个关联表中找到所有匹配的行,如果最后一个关联表无法找到更多的行以后, mysql返回到上一层次关联表,看是否能够找到更多的匹配记录,依次类推迭代执行。照这样的方法查找第一个表记录,再嵌套查询下一个关联表,然后回溯到上一个表,在 mysql中是通过嵌套循环的方式实现(正如其名:嵌套循环关联)

从本质上说, mysql对所有的类型的查询都以同样的方式运行,如: mysql在 from子句中遇到子查询,先执行子查询并将结果放到一个临时表中,然后将这个临时表当作一个普通表对待(正如其名:派生表), mysql在执行 union查询时也使用类似的临时表,在遇到右外连接是, mysql将其改写成等价的左外连接,换而言之,当前版本的 mysql会将所有的查询类型都换换成类似的执行计划,不过,不是所有的查询都可以转换成上面的形式,如:全外连接就无法通过嵌套循环和回溯的方式完成,这也是 mysql并不支持全外连接的原因。

注意: mysql临时表没有任何索引,在编写复杂的子查询和关联查询的时候需要注意这一点,这一点对 union查询也一样,在 mysql5.6和 mariadb中有了重大改变,这两个版本都引入了更加复杂的执行计划。

执行计划:

和很多其他关系数据库不同 ,mysql并不会生成查询字节码来执行查询, mysql生成查询的一棵指令树,然后通过存储引擎执行完成这棵指令树并返回结果,最终的执行计划包含了重构查询的全部信息,如对某个查询执行 explain extended后,再执行 show warnings,就可以查看到重构的查询。 mysql总是从一个表开始一直嵌套循环,回溯完成所有表的关联。所以, mysql的执行计划是一棵左侧深度优先的树 ,然后回溯到上一层关联:

关联查询优化器:

mysql优化器最重要的一部分就是关联查询优化,它决定了多个表关联时的顺序,通常多表关联的时候,可以有多种不同的关联顺序来获得相同的执行效果,关联查询优化器则通过评估不同顺序时的成本来选择一个代价最小的关联顺序。

explain select film.film_id,film.title,film.release_year,actor.actor_id,actor.first_name,actor.last_name from sakila.film join sakila.film_actor using(film_id) join sakila.actor using(actor_id)\G;

*************************** 1. row ***************************

id: 1

select_type: SIMPLE

table: actor

type: ALL

possible_keys: PRIMARY

key: NULL

key_len: NULL

ref: NULL

rows: 200

Extra: NULL

*************************** 2. row ***************************

id: 1

select_type: SIMPLE

table: film_actor

type: ref

possible_keys: PRIMARY,idx_fk_film_id

key: PRIMARY

key_len: 2

ref: sakila.actor.actor_id

rows: 13

Extra: Using index

*************************** 3. row ***************************

id: 1

select_type: SIMPLE

table: film

type: eq_ref

possible_keys: PRIMARY

key: PRIMARY

key_len: 2

ref: sakila.film_actor.film_id

rows: 1

Extra: NULL

3 rows in set (0.00 sec)

执行计划中可以看到, mysql从 actor表开始,使用 film_actor表的索引 film_id来查找对应的 actor_id值,然后根据 film表的主键找到对应的记录, oracle用户会用下面的术语描述: actor表作为驱动表先找到 file_actor表,然后以此结果为驱动表再查找 film表。

下面使用 straight_join关键字指定关联表顺序,如下:

explain select straight_join film.film_id,film.title,film.release_year,actor.actor_id,actor.first_name,actor.last_name from sakila.film join sakila.film_actor using(film_id) join sakila.actor using(actor_id)\G;

*************************** 1. row ***************************

id: 1

select_type: SIMPLE

table: film

type: ALL

possible_keys: PRIMARY

key: NULL

key_len: NULL

ref: NULL

rows: 1000

Extra: NULL

*************************** 2. row ***************************

id: 1

select_type: SIMPLE

table: film_actor

type: ref

possible_keys: PRIMARY,idx_fk_film_id

key: idx_fk_film_id

key_len: 2

ref: sakila.film.film_id

rows: 2

Extra: Using index

*************************** 3. row ***************************

id: 1

select_type: SIMPLE

table: actor

type: eq_ref

possible_keys: PRIMARY

key: PRIMARY

key_len: 2

ref: sakila.film_actor.actor_id

rows: 1

Extra: NULL

3 rows in set (0.00 sec)

从上面的 explain结果上看, mysql自动选择的关联顺序与使用 straight_join关键字的关联顺序完全倒转过来,从上面的对比结果上可以看出, mysql把关联表倒转过来后, film只扫描一行,而 film在最前时扫描行数是 1000.

如果 mysql选择首先扫描 actor表,只会返回 200条记录进行后面的嵌套循环查询,即,倒转关联顺序会让查询进行更少的嵌套循环和回溯操作,为了验证优化器的选择是否正确,我们单独执行了这两个查询,并且看 last_query_cost状态值(这里我使用的虚拟机做测试),结果 mysql选择的顺序预估成本为3883.340463,而我们手工指定的顺序预估成本为4012.731064。

这个简单的示例说明 mysql是如何选项合适的关联顺序来让查询执行的成本尽可能低,重新定义关联的顺序是优化器非常重要的一部分,不过有时候,优化器给出的并不是最优的关联顺序,这时可以使    用 straight_join关键字重写查询,让优化器按照你认为的最优的关联顺序执行,不过,人的判断很难那么精准,所以大部分时候,优化器做出的选择比普通人判断的要更精准。

关联优化器会尝试在所有的关联顺序中选择一个成本最小的来生成执行计划树,如果可能,优化器会遍历每一个表后逐个做嵌套循环计算每一颗可能的执行计划树的成本,最后返回一个最优的执行计划。

但是,糟糕的是,如果有超过 n个表关联,那么需要检查 n的阶乘种关联顺序,我们称之为所有可能的执行计划的 “搜索空间 ”,搜索空间的增长速度非常快,如:若是 10个关联,那么共有 3628800种关联顺序,当搜索空间非常大的时候,优化器不可能逐一评估每一种关联顺序的成本,这时,优化器选择使用贪婪搜索的方式查找最优的关联顺序,实际上,当需要关联的表超过 optimizer_search_depth的限制的时候,就会选择贪婪搜索模式了, optimizer_search_depth这个参数可以根据需要动态指定大小,默认为 62。

出处:小萝卜的博客 http://www.cnblogs.com/xiaoboluo768/ 感谢您的认真阅读。本文版权归作者所有,欢迎转载,但请保留该声明。 最新拙作:《千金良方——MySQL性能优化金字塔法则》,京东有售,图书提供附录提供电子版免费下载,详见链接:http://www.broadview.com.cn/book/5458 关于此书的代码段、部分高清大图和附录已开源,详见(觉得有用的话帮忙顺手点个星星):https://github.com/xiaoboluo768/qianjinliangfang 关于此书基础篇中关于4个系统字典库全面讲解文档已开源,详见(觉得有用的话帮忙顺手点个星星):https://github.com/xiaoboluo768/mysql-system-schema