mysql Join查询与优化思路详解

MySQL Query 的优化—Join的实现原理及优化思路: https://blog.csdn.net/qq_39408664/article/details/119205778


Nested-Loop Join 算法解释

官网Join算法: https://www.docs4dev.com/docs/zh/mysql/5.7/reference/nested-loop-joins.html

Simple Nested-Loop Join

如下图,r为驱动表,s为匹配表,可以看到从 r 中分别取出 r1、r2、…、rn 去匹配 s 表的左右列,然后再合并数据,对 s 表进行了 rn 次访问,对数据库开销大。
mysql Join查询与优化思路详解_第1张图片

Index Nested-Loop Join(索引嵌套)

这个要求非驱动表(匹配表s)上有索引,可以通过索引来减少比较,加速查询。在查询时,驱动表( r )会根据关联字段的索引进行查找,当在索引上找到符合的值,再回表进行查询,也就是只有当匹配到索引以后才会进行回表查询。如果非驱动表(s)的关联键是主键的话,性能会非常高,如果不是主键,要进行多次回表查询,先关联索引,然后根据二级索引的主键ID进行回表操作,性能上比索引是主键要慢。
mysql Join查询与优化思路详解_第2张图片

Block Nested-Loop Join

如果有索引,会选取第二种方式进行 Join ,但如果 Join 列没有索引,就会采用 Block Nested-Loop Join。可以看到中间有个 Join Buffer 缓冲区,是将驱动表的所有 Join 相关的列都先缓存到 Join Buffer 中,然后批量与匹配表进行匹配,将第一种多次比较合并为一次,降低了非驱动表(s)的访问频率。默认情况下 join_buffer_size=256K ,在查找的时候 MySQL 会将所有的需要的列缓存到 Join Buffer 当中,包括 select 的列,而不是仅仅缓存关联列。在一个有 N 个 Join 关联的SQL当中会在执行时候分配 N-1 个 Join Buffer。
mysql Join查询与优化思路详解_第3张图片
join_buffer_size 官方解释:https://www.docs4dev.com/docs/zh/mysql/5.7/reference/server-systemvariables.html

用于普通索引扫描,范围索引扫描和不使用索引的连接的缓冲区的最小大小,从而执行全表扫描。通常,获得快速连接的最佳方法是添加索引。当无法添加索引时,增加 join_buffer_size 的值以获得更快的完全连接。为两个表之间的每个完整连接分配一个连接缓冲区。对于未使用索引的多个表之间的复杂连接,可能需要多个连接缓冲区。




Join 实例的优化

女性客户的数量与平均月薪 & 不同城市的客户数量与平均月薪

可以先通过 explain 进行分析:
mysql Join查询与优化思路详解_第4张图片mysql Join查询与优化思路详解_第5张图片
通过如上分析,第一个SQL使用的是 customers 作为驱动表,而第二个则是使用的是 salary 作为驱动表。之所以选择主要是因为MySQL的优化器会先选取两个表的结构信息,根据表的大小优先选择小表然后再选择大表,但是在其中有条件的情况下会选择与根据条件筛选之后的数据表,相对来说数据会少一些,当然优化就比较简单,分别对于 salary 与 customers 表建立对应的索引就可以(gender,city)和(monthsalary);

建立索引:

建立一个性别与地区的索引
alter table customers add index idx_gender_city(gender,city);
在薪资表中建立月薪相关索引
alter table salary add index idx_monthsalary(monthsalary);

查看查询效率

select count(*) ,avg(s.monthsalary) from customers c,salary s where c.gender = 0 and c.id = s.id;
mysql Join查询与优化思路详解_第6张图片
select count(*),avg(s.monthsalary) from customers c,salary s where c.id = s.id group by c.city;
mysql Join查询与优化思路详解_第7张图片

发现第二句的语句在加了索引后反而效率更低了,我们可以稍微调整下SQL语句:

select count(*),avg(salary.monthsalary) from customers left join salary on customers.id = salary.id group by customers.city;
mysql Join查询与优化思路详解_第8张图片


列出没有手机号码,或者没有照片,或者没有年终奖的客户姓名

首先我们使用 explain 分析一下语句:

explain select name from customers,salary where customers.id=salary.id and (mobile = '0' or photo = '0' or yearbonus = 0);
mysql Join查询与优化思路详解_第9张图片
在这里插入图片描述

现在改为之前的 union all 在前面的方法对于一个表的优化方式:

select name from customers,salary where customers.photo = '0' and customers.id = salary.id
union all
select name from customers,salary where customers.mobile = '0' and customers.id = salary.id
union all
select name from customers,salary where salary.yearbonus = 0 and customers.id = salary.id

mysql Join查询与优化思路详解_第10张图片
执行的效率不理想,可以适当调整SQL语句,如下:

select name from customers where photo = '0' and mobile = '0'
union all
select name from customers where id in (select id from salary where salary.yearbonus = 0);

那么这个时候我们就可以单独针对于如上的两条SQL进行优化实际上最好的优化方式是(建立索引)

alter table customers add index idx_mobile_photo_name(mobile,photo,name);
alter table salary add index idx_yearbonus(yearbonus);

对于 idx_mobile_photo_name 的解释,在 where 中我们对于 mobile , photo , name 定义了一个关联的索引对于MySQL来说:会查找 where 上的(所有)字段是否包含在了某一个索引中,如果存在就会生效。




Join 查询优化思路

  1. 尽可能减少 Join 语句中的 Nested Loop 的循环总次数;如何减少 Nested Loop 的循环总数?最有效的办法只有一个,那就是让驱动表的结果集尽可能的小。为什么?因为驱动结果集越大,意味着需要循环的次数越多,也就是说在被驱动结果集上面所需要执行的查询检索次数会越多。比如,当两个表( 表A和表B )Join 的时候,如果表A 通过 where 条件过滤后有 10 条记录,而表B 有 20 条记录。如果我们选择表A 作为驱动表,也就是被驱动表(表B)的结果集为 20 ,那么我们通过 Join 条件对被驱动表(表B)的比较过滤就会有 10 次。反之,如果我们选择被驱动表(表B)作为驱动表,则需要有 20 次对表A 的比较过滤。当然,此优化的前提条件是通过 Join 条件对各个表的每次访问的资源消耗差别不是太大。如果访问存在较大的差别的时候(一般都是因为索引的区别),我们就不能简单的通过结果集的大小来判断需要 Join 语句的驱动顺序,而是要通过比较循环次数和每次循环所需要的消耗的乘积的大小来得到如何驱动更优化。

  2. 优先优化 Nested Loop 的内层循环;不仅仅是在数据库的 Join 中应该做的,实际上在我们优化程序语言的时候也有类似的优化原则。内层循环是循环中执行次数最多的,每次循环节约很小的资源,在整个循环中就能节约很大的资源。

  3. 保证 Join 语句中被驱动表上 Join 条件字段已经被索引;保证被驱动表上 Join 条件字段已经被索引的目的,正是针对上面两点的考虑,只有让被驱动表的 Join 条件字段被索引了,才能保证循环中每次查询都能够消耗较少的资源,这也正是优化内层循环的实际优化方法。

  4. 当无法保证被驱动表的 Join 条件字段被索引且内存资源充足的前提下,不要太吝啬 Join Buffer 的设置:当在某些特殊的环境中,我们的 Join 必须是 All,index,range 或者是 index_merge 类型的时候, Join Buffer 就会派上用场了。在这种情况下, Join Buffer 的大小将对整个 Join 语句的消耗起到非常关键的作用。




你可能感兴趣的:(MySQL,面试参考题,mysql,mysql优化)