和很多其他关系型数据库不通,MySQL并不会在生成查询字节码来执行查询。MySQL生成查询的一棵指令树,然后通过存储引擎执行完成这棵指令树并返回结果。最终的执行计划包含了重构查询的全部信息。如果某个查询执行EXPLAIN EXTENDED 之后,在执行SHOW WARNINGS,就可以看到重构出的查询。
在计算机科学中,这被称作是一科平和数。但是,这并不是MySQL执行查询的方式。正如我们前面章节介绍的,MySQL总是从一个表开始一直嵌套循环、回溯完成所有表关联。所以,MySQL的执行计划是一个左侧深度优先的树。
关联查询优化器
MySQL优化器最主要的一部分就是关联查询优化,它决定了多个表关联时的顺序。通常多表关联的时候,可以有多重不同的关联顺序来获得相同的执行结果。关联查询优化器则通过评估不同的顺序时的成本来选择一个代价最小的关联顺序。
新的查询通过不同顺序的关联后都可以获得相同的结果:
SELECT film.film_id ,film.title,film.release_year,actor.actor_id ,actor.first_name ,actor.last_name FROM film inner join film_actor USING (film_id)
INNER JOIN actor USING(actor_id).
容易看出,可以通过一些不同的执行计划来完成上面的查询。例如:MySQL可以从film表开始,使用film_actor 表的索引film_id来查找对应的actor_id的值,然后再跟进actor表的主键找到对应的记录。Oracle用户会用下面的术语描述:film表作为驱动表现查找film_actor表,然后以此结果为驱动表在查找actor表。这样的效率应该会不错。我们再使用EXPLAIN 看看MySQL如何执行这个查询。。。
这和我吗前面给出的执行计划完全不同。MySQL从actor表开始,然后与我们前面的计划按照相反的顺序进行关联。这样做是否效率更高呢?我们来看看,先使用STRAIGHT_JOIN 关键字,按照我们之前的顺序执行,
EXPLAIN SELECT STRAIGHT_JOIN film.film_id....
我们来分析一下为什么MySQL会将关联顺序倒转过来:可以看出,关联顺序倒转过来之后的第一个关联表只需要扫描很少的行数。在两种关联顺序之下,第二个和第三个关联表都是根据索引查询的,速度都很快,不同的是需要扫描的索引项的数量不同:
·将film表作为第一个关联表时,会找到951条记录,然后对film_actor 表和actor表畸形嵌套循环查询。
·如果MySQL选择首先扫描actor表,只会返回200条记录然后进行后面的嵌套循环查询。
换句话说,倒转关联顺序会让查询进行跟梢的嵌套循环和回溯操作。为了验证优化器的选择是否正确,我们单独执行者恋歌查询,并且看看对应的last_query_cost 状态值。我们看到倒转的关联顺序的预估成本是241,而原来的查询的预估成本是1154.
这个简单的例子主要想说明MySQL是如何选择合适的关联顺序让查询执行的成本尽可能低的。重新定义关联表的顺序是优化器非常重要的一部分功能。不过有的时候,优化器可能给出的不是最优的关联顺序。这是可以使用straight_join关键字重写查询,让优化器按照你认为最优的关联顺序执行--不过老实说,人的判断很难那么精致,绝大多数的时候,优化器做出的选择都比普通人判断的更要准确。
关联优化器会尝试在所有关联顺序中选择一个成本最小的来生成执行计划树。如果可能优化器会遍历每一个表,然后逐个做循环嵌套计算每一刻可能的执行计划树的成本,最后返回一个最优的执行计划。
不过糟糕的是,如果有超过n给表的关联,那么需要坚持n的阶乘中关联顺序,我们称之为所有可能的执行计划的“搜索空间” ,搜索空间的增长速度非常快--例如,若是有10个表的关联,那么共有362800种可能不同的关联顺序,当搜索空间非常大的时候,优化器不可能逐一评估每一种关联顺序的成本。实际上,当需要关联的表超过optimizer_search_depth的限制的时候,就会攥着“贪婪”搜索模式了。
在MySQL这些年的发展过程中,积累了很多启发式的优化策略来假设执行计划的生成,绝大多数情况下,这都是有效的,以为不会计算每一种关联顺序的成本,索引偶尔也会选择一个不是最优的执行计划。
有时,各个查询的顺序不能随意安排,这是关联优化器可以根据这些规则大大减少索引空间,例如,左连接,相关子查询。这是因为,后面的表的查询需要依赖于前面表的查询结果。这种依赖关系通常可以帮助优化器大大减少需要扫描的执行计划数量。