MySQL5.7——JOIN连接的使用及分析

Wesley13
• 阅读 889

一、MySQL支持的连接类型

MySQL 5.7 官方文档中说明了支持如下四种连接类型:

  1. [INNER|CROSS] JOIN

    JOINCROSS JOININNER JOIN 实现的功能是一致的,它们在语法上是等价的。

    仅在语义上存在区别,CROSS JOIN 特指无条件的连接(没有指定 ON 条件的 JOIN 或者没有指定 WHERE 连接条件的多表 SELECT);INNER JOIN 特指有条件的连接(指定了 ON 条件的 JOIN 或者指定了 WHERE 连接条件的多表 SELECT)。

    当然,如果你非要写 ... CROSS JOIN ... ON ... 这样的语法,也是可以执行的,虽然写着交叉连接,实际上执行的是内连接。

  2. {LEFT|RIGHT} [OUTER] JOIN

    外连接相对于内连接查询结果中多了左表中不满足条件的列。

    例如使用LEFT OUTER JOIN时会保留左表(驱动表)的所有数据(不会受ON条件过滤影响),若右表(被驱动表)无满足连接条件的数据时,用NULL填充列。

  3. NATURAL [{LEFT|RIGHT} [OUTER]] JOIN

    自然连接是等值连接的一种特殊形式,自然连接会自动选取需要连接的两张表中字段名相同的所有列做相等比较,不需要再指定连接条件了。

  4. STRAIGHT_JOIN

    STRAIGHT_JOIN并不在 SQL 标准中,它在表现上和内连接或者交叉连接并无区别,只是一种给 MySQL 优化器的一个提示,STRAIGHT_JOIN 提示 MySQL 按照语句中表的顺序加载表,只有在你明确清楚 MySQL 服务器对你的 JOIN 语句做了负优化的时候才可能用到它。

Tips:

  • | 表示两者皆可出现,[] 表示的是可选的,{} 表示的是必选的,例如 NATURAL LEFT JOINNATURAL JOIN 都是合法的。

二、MySQL表关联的原理

MySQL 使用了**嵌套循环(Nested-Loop Join)**的方式来实现表关联,将表分为驱动表和被驱动表,使用两层循环,先遍历驱动表的每一行,并以此遍历被驱动表的每行,根据连接方式和过滤条件选择数据到输出结果集。

下面介绍循环嵌套算法的基础实现方式——SNLJ,Simple Nested-Loop Join

MySQL5.7——JOIN连接的使用及分析

举个例子来说明SNLJ算法的流程,有如下SQL:

SELECT * FROM A JOIN B ON predication1(A,B) WHERE predication2(A,B) 

使用伪代码来表示关联查询的逻辑,这里重点注意判断ON和WHERE条件的位置,就可以理解下面要讲的两种过滤条件对查询结果集的影响的不同

FOR each row in table_A {// 遍历左表的每一行
    BOOL flag = FALSE;
    FOR each row in table_B such that predication1(row_A, row_B) {// 遍历右表每一行,找到满足join条件的行
        IF predication2(row_A, row_B) {//满足 where 过滤条件
        row:=row_A||row_B;//合并行,加入到输出结果集
        }
        flag=TRUE;// 左表在右表中有对应的行
    }
    IF (!falg) { // 遍历完右表,发现左表在右表中没有有对应的行,则尝试用null补一行
        IF predication2(rouw_A,NULL) {// 补上null后满足 where 过滤条件
            row:=row_A||NULL; // 左表和null补上的行合并,加入到输出结果集
        }
    }
}

Tips:

  • 简单嵌套循环算法缺少对内循环的优化,效率很低,因此MySQL使用了优化后的算法。

    • 处理有索引的join字段时使用INLJ索引嵌套循环算法。内循环中使用B表的聚簇索引或二级索引查询数据,不需要再对B表进行全表扫描。

    • 处理无索引的join字段时使用BNLJ块嵌套循环算法。加入了buffer缓冲区,降低了内循环的次数

    顺便一提,join查询的优化思路就是小表驱动大表,并在大表上创建索引(也就是被驱动表创建索引,如果是聚簇索引最好,就省去了回表扫描)。注意一下,如果在驱动表创建了索引,MySQL是不会使用的。

  • MySQL会根据要执行的SQL语句选择驱动表,可用explain查看执行计划来判断驱动表,总结一下驱动表的选择遵循如下原则:

    • 连接查询没有WHERE条件时

      • 左连接前面的表是驱动表,后面的表是被驱动表

      • 右连接后面的表是驱动表,前面的表是被驱动表

      • 内连接/交叉连接,哪张表的数据少即是驱动表。

    • 连接查询有WHERE条件时

      • 带WHERE条件的表是驱动表

三、ON和WHERE过滤条件的区别

SNLJ算法的伪代码中说明了左连接时是如何处理WHERE和ON过滤条件的,我们先用实际例子来看看实际结果是否符合上述逻辑,再总结ON和WHERE的使用规律。

现在有如下两表:

classes表

class_id

class_name

1

一班

2

二班

3

三班

4

四班

students表

student_id

class_id

student_name

student_gender

1

1

小红

M

2

1

小明

F

3

1

小军

M

4

1

小米

F

5

2

小白

F

6

2

小兵

M

7

2

小林

M

8

3

小新

F

9

3

小王

M

10

3

小丽

F

我们需要找出每个班级的名称及其对应的女同学数量,有两种SQL语句的编写方式:

  •   SELECT class_name, count(student_name) AS num
      FROM classes c LEFT JOIN students s
      ON c.class_id = s.class_id
      AND student_gender = 'F'
      GROUP BY class_name
        
    

    查询结果

    class_name

    num

    一班

    2

    二班

    2

    三班

    1

    四班

    0

  •   SELECT class_name, count(student_name) AS num
      FROM classes c LEFT JOIN students s
      ON c.class_id = s.class_id
      WHERE student_gender = 'F'
      GROUP BY class_name
        
    

    查询结果

    class_name

    num

    一班

    2

    二班

    2

    三班

    1

显然,我们想要的应该是第一种SQL的查询结果,结合伪代码的流程分析下为什么出现这种差异。

使用ON条件限制右表student_gender = 'F'时,遍历完右表,发现左表在右表中没有有对应的行,则尝试用null补一行返回;

使用WHERE条件限制右表student_gender = 'F'时,遍历完右表,发现左表在右表中没有有对应的行,同样用null补一行,但是补上的null不满足WHERE条件,该行不会被返回。

总结下WHERE和ON过滤条件的使用时机

  • 外连接

    • 驱动表限制用WHERE

      如果没有where条件,无论on条件对驱动表进行怎样的限制,驱动表的每一行都至少会返回一次,且这个数据是多余的。

    • 被驱动表限制用ON

      如果想对被驱动表进行限制,则一定要在on条件中进行,若在where中进行则可能导致数据缺失,导致驱动表在被驱动表中无匹配行的行在最终结果中不出现,违背了我们对外连接的理解。

  • 内连接/交叉连接

    • 使用ON和WHERE一样

刚开始写文章,主要是学习过程中的总结,水平有限,如果有什么理解错误的地方,小伙伴们一定要评论告诉我呀~

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
MySQL设置wait_timeout详解
如果你没有修改过MySQL的配置,缺省情况下,wait\_timeout(https://www.oschina.net/action/GoToLink?urlhttp%3A%2F%2Fdev.mysql.com%2Fdoc%2Frefman%2F5.0%2Fen%2Fserversystemvariables.html%23sysvar_w
Wesley13 Wesley13
3年前
MySQL5.7压缩包安装图文教程
MySQL5.7压缩包安装图文教程一、下载网址:https://dev.mysql.com/downloads/(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fdev.mysql.com%2Fdownloads%2F)
Wesley13 Wesley13
3年前
mysql分区译文(第一章)
原文地址(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fdev.mysql.com%2Fdoc%2Frefman%2F5.7%2Fen%2Fpartitioning.html)请注意mysql通用的分区程序已被弃用了(5.7.17),并且将会在mysql8中完全移除
Wesley13 Wesley13
3年前
MySQL 锁(5)
InnoDB引擎有8类锁https://dev.mysql.com/doc/refman/5.7/en/innodblocking.html(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fdev.mysql.com%2Fdoc%2Frefman%2F5.7%2Fen%2
Wesley13 Wesley13
3年前
MySQL 5.7 基于 GTID 的主从复制实践
MySQL5.7基于GTID的主从复制实践PostedbyMikeon20170703运维之美(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fwww.hilinux.com%2F)Home
Wesley13 Wesley13
3年前
MySQL8.0关于caching_sha2_password Plugin的一个Bug
今天在调试使用ansible进行标准化安装MySQL8.0时,发现关于caching\_sha2\_passwordplugin的一个bug。在搭建主从复制时,按照手册说明(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fdev.mysql.com%2Fdoc%2Frefman%2
Stella981 Stella981
3年前
Google地球出现“无法连接到登录服务器(错误代码:c00a0194)”解决方法
Google地球出现“无法连接到登录服务器(错误代码:c00a0194)”解决方法参考文章:(1)Google地球出现“无法连接到登录服务器(错误代码:c00a0194)”解决方法(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fwww.codeprj.com%2Fblo
Wesley13 Wesley13
3年前
MySQL学习(三)
MySQL函数官网:https://dev.mysql.com/doc/refman/5.7/en/funcopsummaryref.html(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fdev.mysql.com%2Fdoc%2Frefman%2F5.7%
Wesley13 Wesley13
3年前
A3
The mysqlpump(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fdev.mysql.com%2Fdoc%2Frefman%2F5.7%2Fen%2Fmysqlpump.html) clientutilityperforms logicalbackups(ht
Wesley13 Wesley13
3年前
MySQL 安装示例数据库(employee、world、sakila、menagerie 等)
sakila示例数据库官方资料及安装说明(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fdev.mysql.com%2Fdoc%2Fsakila%2Fen%2Fsakilainstallation.html),注意查看示例数据库支持的版本是否匹配你的数据库。为了测试,有