在实操之中,对于join、left join、right join通常情况下也是仅仅用到了left join,对于其他几种,心存疑惑,因此对几种join查询、以及原理做个记录。
1.left、right、inner join 的区别
创建表t1、t2
CREATE TABLE `t2` (
`id` int(11) NOT NULL,
`a` int(11) DEFAULT NULL,
`b` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `a` (`a`)
) ENGINE=InnoDB;
create table t1 like t2;
t1、t2分别插入5条数据
delimiter ;;
create procedure idata()
begin
declare i int;
set i=1;
while(i<=5)do
insert into t1 values(i, i, i);
set i=i+1;
end while;
end;;
delimiter ;
call idata();
insert into t2 select * from t1 where a <= 4;
insert into t2 values(6, 6, 6);
left join 会查询出左表所有的数据,以及右表能连接上的字段
select * from t1 left join t2 on t1.id = t2.id;
right join 会查询出右表所有的数据,以及左表能连接上的字段
select * from t1 right join t2 on t1.id = t2.id;
inner join(等值连接) 只返回两个表中联结字段相等的行
select * from t1 inner join t2 on t1.id = t2.id;
2.连接查询原理
为了后面结果更为清晰,往t1再插入15条数据:
drop procedure idata;
delimiter ;;
create procedure idata()
begin
declare i int;
set i=6;
while(i<=20)do
insert into t1 values(i, i, i);
set i=i+1;
end while;
end;;
delimiter ;
call idata();
如果直接使用join语句,MySQL优化器可能会选择表t1或t2作为驱动表,会影响分析SQL语句的执行过程。所以使用straight_join让MySQL使用固定的连接方式执行查询,这样优化器只会按照指定的方式去join。
走索引的情况:
通过a字段连接查询:
explain select * from t2 straight_join t1 on t1.a = t2.a;
t2作为驱动表,t1作为被驱动表,这条语句的explain结果:
因为被驱动表t1的字段a有索引,join过程用上了这个索引,因此这个语句的执行流程是这样的:
- 从表t2中读入一行数据 R;
- 从数据行R中,取出a字段到表t1里去查找;
- 取出表t1中满足条件的行,跟R组成一行,作为结果集的一部分;
- 重复执行步骤1到3,直到表t2的末尾循环结束。
这个过程称之为Index Nested-Loop Join
在这个流程中,扫描t2表5行、之后根据t2.a去表t1中查找,走的是树搜索过程、因此每次扫描一行,总扫描行数为10行。
不走索引的情况
我们再用b字段关联查询
explain select * from t2 straight_join t1 on t1.b = t2.b;
explain结果如下:
由于表t1的字段b上没有索引,所以每次都要把t1表中的每一行,拿出来进行对比, mysql采用的是Block Nested-Loop Join
,Extra可以看到。该算法并没有将表t1全表扫描20次,它的执行流程为:
- 把表t2的数据读入线程内存join_buffer中,由于这个语句中写的是select *,因此是把整个表t2放入了内存;
- 扫描表t1,把表t1中的每一行取出来,跟join_buffer中的数据做对比,满足join条件的,作为结果集的一部分返回。
在这个过程中,对表t1和t2都做了一次全表扫描,因此总扫描次数为25,由于join_buffer是以无序数组的方式组织的,因此对表t1中的每一行,都要做20次判断,总共需要在内存中做的判断次数是:20*5=100次。
因为使用到了join_buffer,而join_buffer大小是有限的,由join_buffer_size
设定,默认为256k。如果一次放不下t2中的所有字段,就会采用分段放的策略,执行策略如下:
- 假如t2表足够大,扫描表t2,顺序读取数据行放入join_buffer中,放完第n行join_buffer满了,继续第2步;
- 扫描表t1,把t1中的每一行取出来,跟join_buffer中的数据做对比,满足join条件的,作为结果集的一部分返回;
- 清空join_buffer;
- 继续扫描表t2,顺序读取最后的m行数据放入join_buffer中,继续执行第2步。
因此表t1可能会被扫描多次,在决定哪个表做驱动表的时候,应该是两个表按照各自的条件过滤,过滤完成之后,计算参与join的各个字段的总数据量,数据量小的那个表,就是“小表”,应该作为驱动表。
三个结论:
- 如果可以使用被驱动表的索引,join语句还是有其优势的;
- 不能使用被驱动表的索引,只能使用Block Nested-Loop Join算法,这样的语句就尽量不要使用;
- 在使用join的时候,应该让小表做驱动表。