详解MySQL 联合查询优化机制


Posted in MySQL onMay 10, 2021

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 联合查询优化机制

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

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

执行计划

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

详解MySQL 联合查询优化机制

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

然而这不是 MySQL 执行查询的方式。如前所述,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 角色(role)功能介绍
Apr 24 MySQL
一看就懂的MySQL的聚簇索引及聚簇索引是如何长高的
May 25 MySQL
Navicat连接MySQL错误描述分析
Jun 02 MySQL
解决mysql模糊查询索引失效问题的几种方法
Jun 18 MySQL
解决Mysql的left join无效及使用的注意事项说明
Jul 01 MySQL
MySQL的索引你了解吗
Mar 13 MySQL
一文了解MYSQL三大范式和表约束
Apr 03 MySQL
数据分析数据库ClickHouse在大数据领域应用实践
Apr 03 MySQL
Mysql查询时间区间日期列表,不会由于数据表数据影响
Apr 19 MySQL
MySQL GTID复制的具体使用
May 20 MySQL
Mysql表数据比较大情况下修改添加字段的方法实例
Jun 28 MySQL
MySQL中LAG()函数和LEAD()函数的使用
Aug 14 MySQL
mysql对于模糊查询like的一些汇总
May 09 #MySQL
MySQL Threads_running飙升与慢查询的相关问题解决
MySQL sql_mode的使用详解
May 08 #MySQL
MySQL 数据丢失排查案例
May 08 #MySQL
MySQL update set 和 and的区别
May 08 #MySQL
MySQL查询学习之基础查询操作
May 08 #MySQL
MySQL sql_mode修改不生效的原因及解决
May 07 #MySQL
You might like
超级简单的php+mysql留言本源码
2009/11/11 PHP
php实现带读写分离功能的MySQL类完整实例
2016/07/28 PHP
一个对于Array的简单扩展
2006/10/03 Javascript
AlertBox 弹出层信息提示框效果实现步骤
2010/10/11 Javascript
jquery 实现表单验证功能代码(简洁)
2012/07/03 Javascript
Extjs3.0 checkboxGroup 动态添加item实现思路
2013/08/14 Javascript
Javascript 正则表达式实现为数字添加千位分隔符
2015/03/10 Javascript
jQuery+css3动画属性制作猎豹浏览器宽屏banner焦点图
2015/03/16 Javascript
php利用curl获取远程图片实现方法
2015/10/26 Javascript
js console.log打印对像与数组用法详解
2016/01/21 Javascript
js制作网站首页图片轮播特效代码
2016/08/30 Javascript
学习Node.js模块机制
2016/10/17 Javascript
easyui-combobox 实现简单的自动补全功能示例
2016/11/08 Javascript
PHP自动加载autoload和命名空间的应用小结
2017/12/01 Javascript
Vue.js做select下拉列表的实例(ul-li标签仿select标签)
2018/03/02 Javascript
js根据json数据中的某一个属性来给数据分组的方法
2018/10/08 Javascript
详解JS实现系统登录页的登录和验证
2019/04/29 Javascript
ES6 proxy和reflect的使用方法与应用实例分析
2020/02/15 Javascript
js实现头像上传并且可预览提交
2020/12/25 Javascript
探究Python中isalnum()方法的使用
2015/05/18 Python
python 日期操作类代码
2018/05/05 Python
对pandas读取中文unicode的csv和添加行标题的方法详解
2018/12/12 Python
python变量赋值方法(可变与不可变)
2019/01/12 Python
详解Python可视化神器Yellowbrick使用
2019/11/11 Python
Python调用Redis的示例代码
2020/11/24 Python
如何用PyPy让你的Python代码运行得更快
2020/12/02 Python
Html5实现用户注册自动校验功能实例代码
2016/05/24 HTML / CSS
Kate Spade美国官网:纽约新兴时尚品牌,以包包闻名于世
2017/11/09 全球购物
竞聘医务工作人员的自我评价分享
2013/11/04 职场文书
商场活动策划方案
2014/01/24 职场文书
运动会跳远广播稿
2014/02/04 职场文书
学习标兵获奖感言
2014/02/20 职场文书
小学生国庆65周年演讲稿范文(2篇)
2014/09/21 职场文书
2014年小学图书室工作总结
2014/12/09 职场文书
Django中session进行权限管理的使用
2021/07/09 Python
MySQL新手入门进阶语句汇总
2022/09/23 MySQL