MySQL连接查询区别及原理
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;
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的时候,应该让小表做驱动表。