到底可不可以使用join
在实际生产中,关于join语句使用的问题,一般会集中在以下两类:
-
我们DBA不让使用join,使用join有什么问题呢?
-
如果有两个大小不同的表做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;`
drop procedure idata;
delimiter ;;
create procedure idata()
begin
declare i int;
set i=1;
while(i<=1000)do
insert into t2 values(i, i, i);
set i=i+1;
end while;
end;;
delimiter ;
call idata();
create table t1 like t2;
insert into t1 (select * from t2 where id<=100)
Index Nested-Loop Join
select * from t1 straight_join t2 on (t1.a=t2.a);
如果直接使用join语句,MySQL优化器可能会选择表t1或t2作为驱动表,这里使用straight_join,让MySQL使用固定的连接方式执行查询。
若使用t1作为驱动表,这个语句的执行流程是这样的:
-
从表t1中读入一行数据 R;
-
从数据行R中,取出a字段到表t2里去查找;
-
取出表t2中满足条件的行,跟R组成一行,作为结果集的一部分;
-
重复执行步骤1到3,直到表t1的末尾循环结束。
我们称之为“Index Nested-Loop Join”,简称NLJ。
它对应的流程图如下所示:
一句话就是:从(驱动表)存储引擎层一行一行的获取数据,将满足条件的数据,根据条件(on)与被驱动表进行匹配,行成每一行数据。
值得注意的是:
在这里驱动表用户的全表扫描,被驱动表是树搜索过程(索引的原因)
Simple Nested-Loop Join
Index Nested-Loop Join中连接被驱动表时,使用的是树搜索,如果被驱动表用不上索引是怎么样的?
现在,我们把SQL语句改成这样:
select * from t1 straight_join t2 on (t1.a=t2.b);
这时候的流程跟Index Nested-Loop Join中的流程差不多,但是对被驱动表的匹配时变了!
Index Nested-Loop Join匹配被驱动表示,利用树搜索一一匹配,
而现在是对被驱动表进行全局扫描进行匹配。
我们称这个为Simple Nested-Loop Join算法。
当然,MySQL也没有使用这个Simple Nested-Loop Join算法,而是使用了另一个叫作“Block Nested-Loop Join”的算法,简称BNL。
Block Nested-Loop Join
当被驱动表中没有索引时,mysql中的BNL算法过程是这样的:
- 把表t1的数据读入线程内存join_buffer中,由于我们这个语句中写的是select *,因此是把整个表t1放入了内存;
- 扫描表t2,把表t2中的每一行取出来,跟join_buffer中的数据做对比,满足join条件的,作为结果集的一部分返回。
这个过程的流程图如下:
相比较Index Nested-Loop Join来讲,两者的时间复杂度一样,但是BNL是将驱动表数据读取在内存中,再进行匹配得,速度更快(Index Nested-Loop Join是从磁盘中读取数据再内存中进行匹配)
总结
一、开头两个问题的解决:
第一个问题:能不能使用join语句?
- 如果可以使用Index Nested-Loop Join算法,也就是说可以用上被驱动表上的索引,其实是没问题的;
- 如果使用Block Nested-Loop Join算法,扫描行数就会过多。尤其是在大表上的join操作,这样可能要扫描被驱动表很多次,会占用大量的系统资源。所以这种join尽量不要用。
所以你在判断要不要使用join语句时,就是看explain结果里面,Extra字段里面有没有出现“Block Nested Loop”字样。
第二个问题是:如果要使用join,应该选择大表做驱动表还是选择小表做驱动表?
-
总是应该使用小表做驱动表。
二、3个算法的理解
Index Nested-Loop Join: 驱动表全局扫描,被驱动表树搜索
Simple Nested-Loop Join:驱动表和被驱动表都是全局扫描
Block Nested-Loop Join: 方便比较,将驱动表数据放内存中(不用来回读取磁盘)