一、MySQL支持的连接类型
MySQL 5.7 官方文档中说明了支持如下四种连接类型:
[INNER|CROSS] JOIN
JOIN
、CROSS JOIN
和INNER JOIN
实现的功能是一致的,它们在语法上是等价的。仅在语义上存在区别,
CROSS JOIN
特指无条件的连接(没有指定ON
条件的JOIN
或者没有指定WHERE
连接条件的多表SELECT
);INNER JOIN
特指有条件的连接(指定了ON
条件的JOIN
或者指定了WHERE
连接条件的多表SELECT
)。当然,如果你非要写
... CROSS JOIN ... ON ...
这样的语法,也是可以执行的,虽然写着交叉连接,实际上执行的是内连接。{LEFT|RIGHT} [OUTER] JOIN
外连接相对于内连接查询结果中多了左表中不满足条件的列。
例如使用
LEFT OUTER JOIN
时会保留左表(驱动表)的所有数据(不会受ON条件过滤影响),若右表(被驱动表)无满足连接条件的数据时,用NULL
填充列。NATURAL [{LEFT|RIGHT} [OUTER]] JOIN
自然连接是等值连接的一种特殊形式,自然连接会自动选取需要连接的两张表中字段名相同的所有列做相等比较,不需要再指定连接条件了。
STRAIGHT_JOIN
STRAIGHT_JOIN
并不在 SQL 标准中,它在表现上和内连接或者交叉连接并无区别,只是一种给 MySQL 优化器的一个提示,STRAIGHT_JOIN
提示 MySQL 按照语句中表的顺序加载表,只有在你明确清楚 MySQL 服务器对你的JOIN
语句做了负优化的时候才可能用到它。
Tips:
|
表示两者皆可出现,[]
表示的是可选的,{}
表示的是必选的,例如NATURAL LEFT JOIN
和NATURAL JOIN
都是合法的。
二、MySQL表关联的原理
MySQL 使用了**嵌套循环(Nested-Loop Join)**的方式来实现表关联,将表分为驱动表和被驱动表,使用两层循环,先遍历驱动表的每一行,并以此遍历被驱动表的每行,根据连接方式和过滤条件选择数据到输出结果集。
下面介绍循环嵌套算法的基础实现方式——SNLJ,Simple Nested-Loop 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一样
刚开始写文章,主要是学习过程中的总结,水平有限,如果有什么理解错误的地方,小伙伴们一定要评论告诉我呀~