详解MySQL 联合查询优化机制

 更新时间:2021年05月10日 11:47:04   作者:岛上码农  
MySQL 使用联合的形式的地方会远远超过我们过去认知的范畴。基本上,它会认为每个查询都有联合,而不仅仅是从两张表中查出匹配的数据行,这包括了子查询,甚至仅仅对单表的 SELECT 操作。因此,理解 MySQL 如何执行联合十分重要。

MySQL 联合查询执行策略。

以一个 UNION 查询为例,MySQL 执行 UNION 查询时,会把他们当做一系列的单个查询语句,然后把对应的结果放入到临时表中,最终再读出来返回。在 MySQL中,每个独立的查询都是一个联合查询,从临时表读取返回结果也一样。

这种情形下,MySQL 的联合查询执行很简单——它将这里的联合查询当做是嵌套循环的联合查询。这意味着 MySQL 会运行一个循环去从数据表读取数据行,然而在运行一个嵌套循环从下一个表读取匹配的数据行。这个过程一直持续,直到找到联合查询中的所有匹配的数据行。然后再根据 SELECT 语句中需要的列去构建返回结果。如下面的查询语句所示:

SELECT tb1.col1, tb2.col2
FROM tb1 INNER JOIN tb2 USING(col3)
WHERE tb1.col1 IN(5,6);

实际转换为 MySQL可能执行的伪代码是下面这样的:

outer_iter = iterator over tb1 where col1 IN(5,6);
outer_row = outer_iter.next;
while outer_row
	inner_iter = iterator over tb2 where col3 = outer_row.col3;
	inner_row = inner_iter.next
    while inner_row
    	output [outer_row.col1, inner_row.col2];
        inner_row = inner_iter.next;
	end
    outer_row = outer.iter.next;
end

转换为伪代码后如下所示

outer_iter = iterator over tb1 where col1 IN(5,6);
outer_row = outer_iter.next;
while outer_row
	inner_iter = iterator over tb2 where col3 = outer_row.col3;
	inner_row = inner_iter.next
    if inner_row
        while inner_row
            output [outer_row.col1, inner_row.col2];
            inner_row = inner_iter.next;
        end
    else
    	output [outer_row.col1, NULL];
	end
    outer_row = outer.iter.next;
end

另一个方式可视化展现查询计划的方式是使用泳道图的形式。下面的图展示了 内连接查询的泳道图。

MySQL 执行的各类查询基本上都是相同的方式。例如,在 FROM 条件里需要先执行的子查询时,也是先将结果放入临时表,然后再把临时表当作普通表后联合来处理。MySQL 执行联合查询时也是使用临时表,然后将右连接查询重写为等价的左连接。简而言之,当前版本的 MySQL 会尽可能把各类查询转成这种方式处理(最新版本 MySQL5.6以后引入了更多的复杂的处理方式)。

当然,并不是所有合法的 SQL 查询语句都可以这么做,有些查询这么做的效果可能很差。

执行计划

MySQL不像其他很多数据库产品,它不会将查询语句产生字节码去执行查询计划。实际上,查询执行计划是一棵指令树,查询执行引擎根据这棵树产生查询结果。最终的查询计划包含了足够多的信息去重构最初的查询。如果在查询语句上执行EXPLAIN EXTENDED(MySQL 8以后不需要加 EXTENDED),然后再执行SHOW WARNINGS,就可以看到重构后的查询。

对于多表查询在概念上可以用树代表。例如,一个4张表的查询可能长得像下面的树一样。这在计算机里称为平衡树,

然而这不是 MySQL 执行查询的方式。如前所述,MySQL 总是从一张数据表开始,然后再从下一张表寻找匹配的数据行。因此,MySQL 的查询计划看起来像下面的左深连接树。

联合查询优化器

MySQL 的查询优化器中最重要的部分是联合查询优化器,由它来决定多表查询执行过程的最优顺序。通常可以通过多种联合查询的次序获取相同的结果。联合查询优化器试图估计这些方案的代价,然后选择最低代价的方案去执行。

下面是一个查询相同结果,但不同次序的联合查询示例。

SELECT film.film_id, film.title, film.release_year, actor.actor_id, actor.first_name, actor.last_name
FROM sakila.film
INNER JOIN sakila.film_actor USING(film_id)
INNER JOIN sakila.actor USING(actor_id);

这里面可能会有一些不同的查询方式。比如,MySQL 可以从 film 表开始,使用 film_actor 的film_id 索引去查找对应的 actor_di 值,然后再从 actor 表使用主键找到对应的 actor 数据行。而 Oracle 用户可能会表述为:“film 表是 film_actor 的驱动表,而 film_actor 是 actor 表的驱动表”。而使用 Explain 解析的结果如下:

******** 1.row ********
id: 1
select_type: SIMPLE
table: actor
type: ALL
possible_keys: PRIMARY
key: NULL
key_len: NULL
ref: NULL
rows: 200
Extra:
******** 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.film.film_id
rows: 1
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: 

这个执行计划与我们猜想的有很大不同。MySQL 首先从 actor 表开始,然后次序是反向的。这是否真的更有效?我们可以在 EXPLAIN 上加上 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
INNER JOIN sakila.film_actor USING(film_id)
INNER JOIN sakila.actor USING(actor_id);
******** 1.row ********
id: 1
select_type: SIMPLE
table: film
type: ALL
possible_keys: PRIMARY
key: NULL
key_len: NULL
ref: NULL
rows: 951
Extra:
******** 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: 1
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: 

这解释了为什么MySQL 为什么需要反序执行查询,这会使得检查的数据行更少。

  • 先查询 film 表会需要对 film_actor 和 actor 进行951次查询(最外层循环)
  • 如果将 actor表前置,则只需要对其他表进行200次查询。

从这个例子可以看出,MySQL 的联合查询优化器可以通过调整查询表次序降低查询代价。重新排序后的联合查询通常是很有效的优化,通常是几倍性能的提高。如果没有性能提高的话,也可以使用 STRAIGHT_JOIN 来避免重排序,而使用我们自己认为最好的查询方式。这种情况实际遇到的会很少,大部分情况下,联合查询优化器都会比人做得更出色。

联合查询优化器视图以最低完成代价构建一个查询执行树。如果有可能,它会从全部的单表计划开始,检查所有可能的子树组合。不幸的是,一个 N 张表的联合查询会有 N 个阶乘的组合次序数量。这被称之为所有可能的查询计划的搜索空间,这个数量增长非常快。一个10张表的联合索引会有3628800个不同的方式!一旦搜索空间增长到过大,会导致查询的优化十分久,这时候服务端会停止做全量分析,替代以类似贪婪算法的方式完成优化。这个数量通过 optimizer_search_depth 系统变量控制,可以自己修改该参数。

相关文章

  • Mysql索引覆盖的实现

    Mysql索引覆盖的实现

    本文主要介绍了Mysql索引覆盖的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-03-03
  • MySQL通过存储过程来添加和删除分区的过程(List分区)

    MySQL通过存储过程来添加和删除分区的过程(List分区)

    这篇文章主要介绍了MySQL-通过存储过程来添加和删除分区(List分区),本文通过创建存储过程来添加和删除分区,可以避免在分区存在时添加分区报错,或者分区不存在时删除分区报错的问题,需要的朋友可以参考下
    2023-09-09
  • mysql创建学生表、课程表及学生选课表详细代码

    mysql创建学生表、课程表及学生选课表详细代码

    这篇文章主要给大家介绍了mysql创建学生表、课程表及学生选课表的相关资料,学生、课程以及成绩的增删改查都是建立在连接数据库的基础之上,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2023-12-12
  • 在MySQL中使用LIMIT进行分页的方法

    在MySQL中使用LIMIT进行分页的方法

    这篇文章主要介绍了在MySQL中使用LIMIT进行分页的方法,作者列举出了三种方法,并且针对跳页等常见问题做出了提示,需要的朋友可以参考下
    2015-05-05
  • MySQL之同表一个字段如何赋值给另一个字段

    MySQL之同表一个字段如何赋值给另一个字段

    这篇文章主要介绍了MySQL之同表一个字段如何赋值给另一个字段问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07
  • 浅谈sql连接查询的区别 inner,left,right,full

    浅谈sql连接查询的区别 inner,left,right,full

    下面小编就为大家带来一篇浅谈sql连接查询的区别 inner,left,right,full。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-10-10
  • MySQL InnoDB存储引擎的深入探秘

    MySQL InnoDB存储引擎的深入探秘

    这篇文章主要给大家介绍了关于MySQL InnoDB存储引擎的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-02-02
  • 保存图片到MySQL以及从MySQL读取图片全过程

    保存图片到MySQL以及从MySQL读取图片全过程

    有人喜欢使用mysql来存储图片,而有的人喜欢把图片存储在文件系统中,而当我们要处理成千上万的图片时,会引起技术问题,下面这篇文章主要给大家介绍了关于如何保存图片到MySQL以及从MySQL读取图片的相关资料,需要的朋友可以参考下
    2023-05-05
  • MySQL语句整理及汇总介绍

    MySQL语句整理及汇总介绍

    今天小编就为大家分享一篇关于MySQL语句整理及汇总介绍,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • MySQL定时器开启、调用实现代码

    MySQL定时器开启、调用实现代码

    有些新手朋友对MySQL定时器开启、调用不是很熟悉,本人整理测试一些,拿出来和大家分享一下,希望可以帮助你们
    2012-12-12

最新评论