left join左表一定是驱动表吗?
left join左表一定是驱动表吗?
日常工作中,遇到很多left join的SQL,今天对left join的这种语法进行简单讲解。刚开始接触MySQL的时候,我也认为使用left join的时候,是左表驱动右表的,但是随着对MySQL理解的深入,时间长了发现这个理解是错误的。
我们先来看个例子:
mysql> create table a(f1 int, f2 int, index(f1))engine=innodb;
Query OK, 0 rows affected (0.01 sec)
mysql> create table b(f1 int, f2 int)engine=innodb;
Query OK, 0 rows affected (0.01 sec)
mysql> insert into a values(1,1),(2,2),(3,3),(4,4),(5,5),(6,6);
Query OK, 6 rows affected (0.00 sec)
Records: 6 Duplicates: 0 Warnings: 0
mysql> insert into b values(3,3),(4,4),(5,5),(6,6),(7,7),(8,8);
Query OK, 6 rows affected (0.02 sec)
Records: 6 Duplicates: 0 Warnings: 0
首先我们创建2个表,表a和表b,2个表的结构一致,其中表a的f1字段有索引,表b没有索引。
来看下面两条SQL
select * from a left join b on(a.f1=b.f1)
and (a.f2=b.f2); /*SQL 1*/
select * from a left join b on(a.f1=b.f1)
where (a.f2=b.f2);/*SQL 2*/
mysql> select * from a left join b on(a.f1=b.f1) and (a.f2=b.f2);
+------+------+------+------+
| f1 | f2 | f1 | f2 |
+------+------+------+------+
| 3 | 3 | 3 | 3 |
| 4 | 4 | 4 | 4 |
| 5 | 5 | 5 | 5 |
| 6 | 6 | 6 | 6 |
| 1 | 1 | NULL | NULL |
| 2 | 2 | NULL | NULL |
+------+------+------+------+
6 rows in set (0.01 sec)
mysql> select * from a left join b on(a.f1=b.f1) where (a.f2=b.f2);
+------+------+------+------+
| f1 | f2 | f1 | f2 |
+------+------+------+------+
| 3 | 3 | 3 | 3 |
| 4 | 4 | 4 | 4 |
| 5 | 5 | 5 | 5 |
| 6 | 6 | 6 | 6 |
+------+------+------+------+
4 rows in set (0.01 sec)
从结果可以看到,这两条SQL返回的值是不一样的,SQL1中,将表b中不存在的记录用null来进行表示,和表a中的记录进行了连接查询。SQL2中,只将表a和表b中共有的记录进行了连接查询。
SQL2的原因可以解释为a.f2 != b.f2,所以不满足where条件。
接下来,来看这两个SQL的执行计划,先看SQL1:
mysql> explain select * from a left join b on(a.f1=b.f1) and (a.f2=b.f2);
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| 1 | SIMPLE | a | NULL | ALL | NULL | NULL | NULL | NULL | 6 | 100.00 | NULL |
| 1 | SIMPLE | b | NULL | ALL | NULL | NULL | NULL | NULL | 6 | 100.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)
SQL1的执行计划中不难看出来,表a作为了驱动表,表b作为了被驱动表,之所以做出这个判断,是因为在连接查询的执行计划中,每个表都会对应一条记录,这些记录的id列的值是相同的,出现在前边的表表示驱动表,出现在后边的表表示被驱动表。由于表b上的f1没有索引,所以使用了BNL算法,如果表b上的f1有索引,则会使用INLJ算法。
再来看SQL 2的执行计划:
mysql> explain select * from a left join b on(a.f1=b.f1) where (a.f2=b.f2);
+----+-------------+-------+------------+------+---------------+------+---------+-----------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+-----------+------+----------+-------------+
| 1 | SIMPLE | b | NULL | ALL | NULL | NULL | NULL | NULL | 6 | 100.00 | Using where |
| 1 | SIMPLE | a | NULL | ref | f1 | f1 | 5 | test.b.f1 | 1 | 16.67 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+-----------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
从这个执行计划中可以看到,表b作为了这个SQL的驱动表,表a作为了被驱动表,这个SQL的执行过程是这样的:顺序扫描表b,并将表b的字段放入join buffer,对于join buffer中表b的每一行用b.f1到表a中去查,匹配到记录后判断a.f2=b.f2是否满足,满足条件的话就作为结果集的一部分返回。
SQL2中,因为where条件中,NULL跟任何值执行等值判断和不等值判断的结果,都是NULL,而where null不会输出任何结果集,如下:
mysql> select * from a where null;
Empty set (0.00 sec)
mysql> select * from a ;
+------+------+
| f1 | f2 |
+------+------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
| 5 | 5 |
| 6 | 6 |
+------+------+
6 rows in set (0.00 sec)
所以,where条件得到的结果集中不会包含null值相关的列。
我们再来看看SQL2的explain结果中的warnings:
mysql> explain select * from a left join b on(a.f1=b.f1) where (a.f2=b.f2);
+----+-------------+-------+------------+------+---------------+------+---------+-----------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+-----------+------+----------+-------------+
| 1 | SIMPLE | b | NULL | ALL | NULL | NULL | NULL | NULL | 6 | 100.00 | Using where |
| 1 | SIMPLE | a | NULL | ref | f1 | f1 | 5 | test.b.f1 | 1 | 16.67 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+-----------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
mysql> show warnings;
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note | 1003 | /* select#1 */ select `test`.`a`.`f1` AS `f1`,`test`.`a`.`f2` AS `f2`,`test`.`b`.`f1` AS `f1`,`test`.`b`.`f2` AS `f2` from `test`.`a` join `test`.`b` where ((`test`.`a`.`f1` = `test`.`b`.`f1`) and (`test`.`a`.`f2` = `test`.`b`.`f2`)) |
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
可以看到,MySQL的优化器把这条语句的left join改写成了join,然后因为表a的f1上有索引,就把表b作为驱动表,这样就可以用上表a的f1索引。
这个例子说明了两点
1、即使我们在SQL语句中写成left join,执行过程还是有可能不是从左到右连接的。也就是说,使用left join时,左边的表不一定是驱动表。
2、如果需要left join的语义,就不能把被驱动表的字段放在where条件里面做等值判断或不等值判断,必须都写在on里面。
如果我们将上面SQL中的left join写成join呢?
select * from a join b on(a.f1=b.f1)
and (a.f2=b.f2); /*SQL 1*/
select * from a join b on(a.f1=b.f1)
where (a.f2=b.f2);/*SQL 2*/
mysql> explain select * from a join b on(a.f1=b.f1) and (a.f2=b.f2);
+----+-------------+-------+------------+------+---------------+------+---------+-----------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+-----------+------+----------+-------------+
| 1 | SIMPLE | b | NULL | ALL | NULL | NULL | NULL | NULL | 6 | 100.00 | Using where |
| 1 | SIMPLE | a | NULL | ref | f1 | f1 | 5 | test.b.f1 | 1 | 16.67 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+-----------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
mysql> show warnings;
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note | 1003 | /* select#1 */ select `test`.`a`.`f1` AS `f1`,`test`.`a`.`f2` AS `f2`,`test`.`b`.`f1` AS `f1`,`test`.`b`.`f2` AS `f2` from `test`.`a` join `test`.`b` where ((`test`.`a`.`f2` = `test`.`b`.`f2`) and (`test`.`a`.`f1` = `test`.`b`.`f1`)) |
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> explain select * from a join b on(a.f1=b.f1) where (a.f2=b.f2);
+----+-------------+-------+------------+------+---------------+------+---------+-----------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+-----------+------+----------+-------------+
| 1 | SIMPLE | b | NULL | ALL | NULL | NULL | NULL | NULL | 6 | 100.00 | Using where |
| 1 | SIMPLE | a | NULL | ref | f1 | f1 | 5 | test.b.f1 | 1 | 16.67 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+-----------+------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)
mysql> show warnings;
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note | 1003 | /* select#1 */ select `test`.`a`.`f1` AS `f1`,`test`.`a`.`f2` AS `f2`,`test`.`b`.`f1` AS `f1`,`test`.`b`.`f2` AS `f2` from `test`.`a` join `test`.`b` where ((`test`.`a`.`f1` = `test`.`b`.`f1`) and (`test`.`a`.`f2` = `test`.`b`.`f2`)) |
+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
可以通过结果看到,优化器将这两条SQL改写成了相同的结果。
- 微信小程序:字体保持大小
- 在为知笔记中使用JQuery
- 在为知笔记中使用JQuery
- Python学习笔记(1):列表元组结构
- 6步创建一个通用机器学习模板
- Python学习笔记(2):数据库访问
- 学习Python语言,这些酷毙的工具你知道几个?
- Mysql备份系列(4)--lvm-snapshot备份mysql数据(全量+增量)操作记录
- 免费体验国内首款写作机器人,就是这么溜!
- Python学习笔记(3):数据集操作-列的统一操作
- Python学习笔记(3):数据集操作-列的统一操作
- Python学习笔记(4):自定义时间类
- Mysql备份系列(3)--innobackupex备份mysql大数据(全量+增量)操作记录
- 微信小程序开发:设置消息推送
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 解决django 多个APP时 static文件的问题
- Android Studio如何获取SQLite数据并显示到ListView上
- Android自定义短信倒计时view流程分析
- 使用Android studio3.6的java api方式调用opencv
- thinkphp5 + ajax 使用formdata提交数据(包括文件上传) 后台返回json完整实例
- php+iframe 实现上传文件功能示例
- php5.3/5.4/5.5/5.6/7常见新增特性汇总整理
- 如何在OpenStack Kolla上部署Tungsten Fabric(附14个常见的配置问题)
- 浮点数的秘密
- 由多线程内存溢出产生的实战分析
- 消失的Java进程-Linux OOM Killer
- Myers‘Diff之贪婪算法
- DiffUtil和它的差量算法
- C语言高效编程与代码优化
- C/C++函数指针与指针函数