暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

拿什么拯救你,我的MySQL子查询

恒生DBA公社 2021-04-21
739



前言导读


mysql的子查询一直被人所诟病,主要原因是mysql子查询奉行的是“由外到内”的政策。首先运行外部表达式,然后把外部表达式的值传递给子查询,子查询再将自己的运行结果与外部表达式的值进行比较,匹配的话则返回记录,这样在外部表的记录比较多的情况下,效率是很低的。所以许多的公司都在自己的开发规范中明确规定了不能使用子查询,一律以表关联操作来代替。其实mysql对子查询还是做了一些优化的,特别是在mysql5.6中,推出了多个针对子查询的优化措施,一个很重要的技术就是semi_join




原理分析

所谓的semi_join是相对inner join而说的,在inner join的场景下,左节点的表必须对右节点的表中每条匹配记录都返回一条记录,这样在右节点有多条记录匹配的话,左节点也必须返回多条重复记录,会造成一些不必要的性能损失。

      而在另外一些场景中,我们只关心的是在另外一个表中是否存在相匹配的记录,而不是有几条记录匹配。这时候我们就可以使用到semi_join的技术了。在semi_join的方法中,无论右节点有几条记录匹配,左节点都只返回一条相同记录。并且semi_join只返回外部查询表中的记录,而不会返回内部查询表中的记录。这也是我们进行semi_join优化的基础,即我们只需要从semi_join中获取最少量的,足以对外部表进行筛选的信息就够了。所谓的最少量,体现在优化上就是如何去重。



案例演示

假设有两张表classroster



class表中记录的是课程号和课程名称,字段class_num具有唯一值。

roster记录的是课程号和参加该课程的学号,多个学生可能参加同一门课程,所以字段class_num有多个值。

 

如果要查看哪些课程有学生参加了,可以使用inner join的方式

mysqlSELECT class.class_num, class.class_name FROM class INNER JOIN roster WHERE class.class_num = roster.class_num;      


 

在这里class_num 等于3的记录重复了,而我们这时候只需要知道有哪些课程有学生参加了,并不需要知道有多少个学生参加,所以这里的重复记录对我们来说并没有意义。虽然我们可以用distinct来去重,但这显然会影响性能,这时候我们就可以用到子查询了。


mysql
>  SELECT class_num, class_name FROM class WHERE class_num IN (SELECT class_num FROM roster);  



    

这个子查询中有两个要素需要注意:

1. 所查询的记录的字段都来自于class表,roster表中的字段并没有参与

2. 虽然roster表中有多条记录匹配,但class表中只返回一条记录

 

下面是mysql 重构后的sql的形式,提示已经使用了semi_join

/* select#1 */ select`employees`.`class`.`class_num` AS `class_num`,

`employees`.`class`.`class_name` AS`class_name` from `employees`.`class` 

semi join (`employees`.`roster`) where(`employees`.`class`.`class_num` = 

`<subquery2>`.`class_num`) 




mysql 支持的semi_join的策略


mysql支持的semi_join的策略有4个,分别如下:

1. DuplicateWeedout: 像表连接一样运行semi_join,然后用临时表来消除重复记录。


2. FirstMatch: 当扫描内部表时,发现有多条记录与外部表匹配时,只返回第一条记录,而不是全部返回。这种方式减少了许多不必要的记录的产生。


3. LooseScan: 把inner-table数据基于索引进行分组,取每组第一条数据进行匹配。

4. Materialize: 将inner-table去重固化成一个带索引的临时表,索引可以被用来去除重复记录,或者被用来与外部表进行关联。





使用semi_join需要具备的条件


1. 必须是形式类似于下面的IN子查询

  SELECT ...

   FROMot1, ...

  WHERE (oe1, ...) IN (SELECT ie1, ... FROM it1, ... WHERE ...);

2. 子查询必须是个单独的SELECT操作,而不能用UNION进行合并

3. 子查询不能使用GROUP BY或HAVING以及其它聚集函数

4. 不能够在ORDER BY 中跟随LIMIT

5. 外部表数量与内部表的数量只和不能超过最大表关联的数量

6. optimizer_switch参数中的semi_join必须设置为ON



semi_join的执行计划显示


1. 当使用了DuplicateWeedout的策略时,会在Extra列中显示Start temporary和End temporary的提示


2.当使用了FirstMatch策略时,会在Extra列中显示 FirstMatch(tbl_name)的提示


3.当使用了 LooseScan策略时,会在Extra列中显示 LooseScan(m..n)的提示,m和n是索引分组号




4.当使用了Materialize策略时,分两种情况:

  1) 在5.6.7之前,如果是单表的话会在Extra列中显示Materialize的提示;如果是多表的话会在Exatra列中显示 Start materialize and End materialize的提示;

 2) 在5.6.7之后,会在select_type列中显示 MATERIALIZED ,并在rows列中显示物化的行数。



参考文档:

https://dev.mysql.com/doc/refman/5.6/en/semi-joins.html

https://mariadb.com/kb/en/library/semi-join-subquery-optimizations/

https://mariadb.com/kb/en/library/loosescan-strategy/



不想走丢的话,请关注公众号 恒生DBA公社:hs_dba

让时代更多元,让工作更高效










文章转载自恒生DBA公社,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论