正确理解Left join

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 通俗来讲,left join就是以左表作为主表,结果返回左表的所有记录,右表满足条件记录正常显示,满足条件记录使用NULL做填充,一般业务中我们需要显示左表全部记录时才会使用left join。另外,某些情况下MySQL优化器会将我们的left join改写为join,什么情况下MySQL会做这样的优化?

1.1 测试数据

root@mysql 11:24:  [db1]> show create table t1\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `uid` int(11) NOT NULL AUTO_INCREMENT,
  `uname` varchar(10) DEFAULT NULL,
  `is_delete` int(11) DEFAULT '0',
  PRIMARY KEY (`uid`),
  KEY `idx_uname` (`uname`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

root@mysql 11:24:  [db1]> show create table t2\G
*************************** 1. row ***************************
       Table: t2
Create Table: CREATE TABLE `t2` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `uname` varchar(10) DEFAULT NULL,
  `uage` varchar(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_uname` (`uname`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)

root@mysql 11:24:  [db1]> select * from t1;
+-----+-------+-----------+
| uid | uname | is_delete |
+-----+-------+-----------+
|   1 | aa    |         0 |
|   2 | bb    |         0 |
|   3 | cc    |         0 |
|   4 | dd    |         0 |
|   5 | ee    |         0 |
+-----+-------+-----------+
5 rows in set (0.00 sec)

root@mysql 11:24:  [db1]> select * from t2;
+----+-------+------+
| id | uname | uage |
+----+-------+------+
|  1 | aa    | 12   |
|  2 | aa    | 13   |
|  5 | cc    | 12   |
+----+-------+------+
3 rows in set (0.00 sec)

1.2 条件放在on和where之前的区别

1、从结果显示来看

从结果显示来看两者是完全不同的:

1)当把过滤条件写在and上时,返回结果集中会显示左表全部记录,右表满足条件的记录正常显示,不满足条件的记录显示为NULL,是我们通俗理解上left join应该显示的结果;

2)而当把过滤条件写在where上时,虽然SQL中我们使用了left join去做表关联,但是实际结果集中并不是我们想要的返回,结果只是返回了满足条件的所有记录。


root@mysql 11:24:  [db1]>  select * from t1 left join t2 on t1.uname=t2.uname and t2.uage>12;
+-----+-------+-----------+------+-------+------+
| uid | uname | is_delete | id   | uname | uage |
+-----+-------+-----------+------+-------+------+
|   1 | aa    |         0 |    2 | aa    | 13   |
|   2 | bb    |         0 | NULL | NULL  | NULL |
|   3 | cc    |         0 | NULL | NULL  | NULL |
|   4 | dd    |         0 | NULL | NULL  | NULL |
|   5 | ee    |         0 | NULL | NULL  | NULL |
+-----+-------+-----------+------+-------+------+
5 rows in set (0.00 sec)

root@mysql 11:25:  [db1]>  select * from t1 left join t2 on t1.uname=t2.uname where t2.uage>12;
+-----+-------+-----------+------+-------+------+
| uid | uname | is_delete | id   | uname | uage |
+-----+-------+-----------+------+-------+------+
|   1 | aa    |         0 |    2 | aa    | 13   |
+-----+-------+-----------+------+-------+------+
1 row in set (0.00 sec)

2、从执行计划来看

1)当把过滤条件写在and上时,执行计划没有做过多的改写,左表t1作为驱动表与t2进行关联查询;

2)当把过滤条件写在where上时,我们发现MySQL对原SQL进行了改写,最重要的一点是将left join改写为join,这个动作就导致SQL在执行计划中会优先选择小表作为驱动表,而并不一定是左表t1作为驱动表。

root@mysql 11:26:  [db1]> explain  select * from t1 left join t2 on t1.uname=t2.uname and t2.uage>12;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                              |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    5 |   100.00 | NULL                                               |
|  1 | SIMPLE      | t2    | NULL       | ALL  | idx_uname     | NULL | NULL    | NULL |    3 |   100.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

root@mysql 11:26:  [db1]> show warnings\G
*************************** 1. row ***************************
  Level: Note
   Code: 1003
Message: /* select#1 */ select `db1`.`t1`.`uid` AS `uid`,`db1`.`t1`.`uname` AS `uname`,`db1`.`t1`.`is_delete` AS `is_delete`,`db1`.`t2`.`id` AS `id`,`db1`.`t2`.`uname` AS `uname`,`db1`.`t2`.`uage` AS `uage` from `db1`.`t1` left join `db1`.`t2` on(((`db1`.`t2`.`uname` = `db1`.`t1`.`uname`) and (`db1`.`t2`.`uage` > 12))) where 1
1 row in set (0.00 sec)

root@mysql 11:26:  [db1]>
root@mysql 11:26:  [db1]>
root@mysql 11:26:  [db1]> explain  select * from t1 left join t2 on t1.uname=t2.uname where t2.uage>12;
+----+-------------+-------+------------+------+---------------+-----------+---------+--------------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key       | key_len | ref          | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+-----------+---------+--------------+------+----------+-------------+
|  1 | SIMPLE      | t2    | NULL       | ALL  | idx_uname     | NULL      | NULL    | NULL         |    3 |    33.33 | Using where |
|  1 | SIMPLE      | t1    | NULL       | ref  | idx_uname     | idx_uname | 43      | db1.t2.uname |    1 |   100.00 | NULL        |
+----+-------------+-------+------------+------+---------------+-----------+---------+--------------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

root@mysql 11:26:  [db1]> show warnings\G
*************************** 1. row ***************************
  Level: Note
   Code: 1003
Message: /* select#1 */ select `db1`.`t1`.`uid` AS `uid`,`db1`.`t1`.`uname` AS `uname`,`db1`.`t1`.`is_delete` AS `is_delete`,`db1`.`t2`.`id` AS `id`,`db1`.`t2`.`uname` AS `uname`,`db1`.`t2`.`uage` AS `uage` from `db1`.`t1` join `db1`.`t2` where ((`db1`.`t1`.`uname` = `db1`.`t2`.`uname`) and (`db1`.`t2`.`uage` > 12))
1 row in set (0.00 sec)

1.3 left join .. where .. is null 的使用

基于以上的案例,我们可以得出以下结论:

1)left join会返回左表所有记录,右表满足过滤条件记录正常反馈,不满足记录返回NULL处理,join只会返回两表均满足过滤条件的记录

2)left join把过滤条件写在on条件上时才是我们通俗理解上的left join,而left join中将表过滤条件写在where上时,MySQL会把left join改写为join。

3)left join关联查询时,表的驱动顺序是确定的,左表作为驱动表与右表进行关联查询,但是若MySQL优化器将left join改写为join的情况下,MySQL就会优先选择小表作为驱动表进行关联查询,一定程度上提升了SQL的执行效率。

但是,对于第一/二条,将过滤条件放在where条件上的时候,MySQL优化器就一定会将left join 改写为join吗?left join一定会返回左表全部记录吗?答案显然是“不一定的”,以下就为大家展示一个特例。

root@mysql 11:42:  [db1]> select * from t1 left join t2 on t1.uname=t2.uname;
+-----+-------+-----------+------+-------+------+
| uid | uname | is_delete | id   | uname | uage |
+-----+-------+-----------+------+-------+------+
|   1 | aa    |         0 |    1 | aa    | 12   |
|   1 | aa    |         0 |    2 | aa    | 13   |
|   3 | cc    |         0 |    5 | cc    | 12   |
|   2 | bb    |         0 | NULL | NULL  | NULL |
|   4 | dd    |         0 | NULL | NULL  | NULL |
|   5 | ee    |         0 | NULL | NULL  | NULL |
+-----+-------+-----------+------+-------+------+
6 rows in set (0.00 sec)

root@mysql 11:41:  [db1]> explain  select * from t1 left join t2 on t1.uname=t2.uname where t2.uage is null;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                              |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
|  1 | SIMPLE      | t1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    5 |   100.00 | NULL                                               |
|  1 | SIMPLE      | t2    | NULL       | ALL  | idx_uname     | NULL | NULL    | NULL |    3 |    33.33 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

root@mysql 11:41:  [db1]> show warnings\G
*************************** 1. row ***************************
  Level: Note
   Code: 1003
Message: /* select#1 */ select `db1`.`t1`.`uid` AS `uid`,`db1`.`t1`.`uname` AS `uname`,`db1`.`t1`.`is_delete` AS `is_delete`,`db1`.`t2`.`id` AS `id`,`db1`.`t2`.`uname` AS `uname`,`db1`.`t2`.`uage` AS `uage` from `db1`.`t1` left join `db1`.`t2` on((`db1`.`t2`.`uname` = `db1`.`t1`.`uname`)) where isnull(`db1`.`t2`.`uage`)
1 row in set (0.00 sec)

root@mysql 11:41:  [db1]> select * from t1 left join t2 on t1.uname=t2.uname where t2.uage is null;
+-----+-------+-----------+------+-------+------+
| uid | uname | is_delete | id   | uname | uage |
+-----+-------+-----------+------+-------+------+
|   2 | bb    |         0 | NULL | NULL  | NULL |
|   4 | dd    |         0 | NULL | NULL  | NULL |
|   5 | ee    |         0 | NULL | NULL  | NULL |
+-----+-------+-----------+------+-------+------+
3 rows in set (0.00 sec)

可以看到,我们使用left join,但是where 过滤条件是右表某些字段is null的查询时。首先从执行计划来看,MySQL优化器并没有将left join改写为join;然后从结果返回来看,可以看到该SQL仅仅返回了满足过滤条件的记录,并没有返回左表全部记录。

对于left join,过滤条件是右表某字段is null的情况是一个特例,而且这种写法经常被DBA同学用来做业务上一些not in/not exists的改写优化。

1.4 结论

1)left join会返回左表所有记录,右表满足过滤条件记录正常反馈,不满足记录返回NULL处理,join只会返回两表均满足过滤条件的记录

2)left join把过滤条件写在on条件上时才是我们通俗理解上的left join,而left join中将表过滤条件写在where上时,MySQL会把left join改写为join。

3)left join且where过滤条件为右表某字段is null时属于一个特例,该情况下MySQL不会将left join改写为join,从而其表关联查询的顺序也只能是左表作为驱动表与右表进行关联查询。

4)left join关联查询时,表的驱动顺序是确定的,左表作为驱动表与右表进行关联查询,但是若MySQL优化器将left join改写为join的情况下,MySQL就会优先选择小表作为驱动表进行关联查询,一定程度上提升了SQL的执行效率。

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
SQL 关系型数据库 MySQL
MySql 别犯糊涂了! LEFT JOIN 的 ON 后接上筛选条件,多个条件会出事!
MySql 别犯糊涂了! LEFT JOIN 的 ON 后接上筛选条件,多个条件会出事!
1794 0
MySql 别犯糊涂了! LEFT JOIN 的 ON 后接上筛选条件,多个条件会出事!
|
8月前
|
关系型数据库 MySQL
left join、right join和join,傻傻分不清?
真的是一张图道清所有join的区别啊,可惜我还是看不懂,可能人比较懒,然后基本一个left join给我就是够用的了,所以就没怎么去仔细研究了,但是现实还是逼我去搞清楚,索性自己动手,总算理解图中的含义了,下面就听我一一道来。
72 1
|
10月前
|
关系型数据库 MySQL
inner join 、left join、right join,优先使用inner join
inner join 、left join、right join,优先使用inner join
【SQL开发实战技巧】系列(六):从执行计划看NOT IN、NOT EXISTS 和 LEFT JOIN效率,记住内外关联条件不要乱放
从执行计划看NOT IN、NOT EXISTS 和 LEFT JOIN效率,还是那就话,别死记网上结论、在使用内外关联时,特别是简写方式时记住关联条件不要乱放!【SQL开发实战技巧】这一系列博主当作复习旧知识来进行写作,毕竟SQL开发在数据分析场景非常重要且基础,面试也会经常问SQL开发和调优经验,相信当我写完这一系列文章,也能再有所收获,未来面对SQL面试也能游刃有余~。
【SQL开发实战技巧】系列(六):从执行计划看NOT IN、NOT EXISTS 和 LEFT JOIN效率,记住内外关联条件不要乱放
|
关系型数据库 MySQL
八、inner join 、left join、right join,优先使用inner join
八、inner join 、left join、right join,优先使用inner join
171 0
|
SQL 关系型数据库 MySQL
Join,left join,right join(1)--连接原理(三十九)
Join,left join,right join(1)--连接原理(三十九)
|
SQL 缓存 算法
写出好的Join语句,前提你得懂这些
因为驱动结果集越大,意味着需要循环的次数越多,也就是说在被驱动结果集上面所 需要执行的查询检索次数会越多。比如,当两个表(表 A 和 表 B) Join 的时候,如果表 A 通过 WHERE 条件过滤后有 10 条记录,而表 B 有 20 条记录。如果我们选择表 A 作为驱动表,也就是被驱动表的结果集为 20,那么我们通过 Join 条件对被驱动表(表 B)的比较过滤就会有 10 次。反之,如果我们选择表 B 作为驱动表,则需要有 20 次对表 A 的比较过滤。小贴士1:驱动表的定义:当进行多表连接查询时,1.指定了联接条件时,满足查询条件的记录行数少的表为驱动表,2.未指定联接条件时,行数少
|
SQL 语音技术 数据库
SQL基础【十五、join、Inner join、Left join、Right join、Full join】
SQL基础【十五、join、Inner join、Left join、Right join、Full join】
130 0
SQL基础【十五、join、Inner join、Left join、Right join、Full join】
|
SQL 数据库
left join 后用 on 还是 where,区别大了!
前天写SQL时本想通过 A left B join on and 后面的条件来使查出的两条记录变成一条,奈何发现还是有两条。 后来发现 join on and 不会过滤结果记录条数,只会根据and后的条件是否显示 B表的记录,A表的记录一定会显示。 不管and 后面的是A.id=1还是B.id=1,都显示出A表中所有的记录,并关联显示B中对应A表中id为1的记录或者B表中id为1的记录。
107 0
left join 后用 on 还是 where,区别大了!
SQL优化--inner、left join替换in、not in、except
SQL优化--inner、left join替换in、not in、except新系统上线,用户基数16万,各种查询timeout。打开砂锅问到底,直接看sql语句吧,都是泪呀,一大堆innot inexcept。
1305 0