在实操之中,对于join、left join、right join通常情况下也是仅仅用到了left join,对于其他几种,心存疑惑,因此对几种join查询、以及原理做个记录。

MYSQL全连接查询不能用 mysql连接查询原理_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;

 

MYSQL全连接查询不能用 mysql连接查询原理_MYSQL全连接查询不能用_02

right join 会查询出右表所有的数据,以及左表能连接上的字段

select * from t1 right join t2 on t1.id = t2.id;

MYSQL全连接查询不能用 mysql连接查询原理_字段_03

inner join(等值连接) 只返回两个表中联结字段相等的行

select * from t1 inner join t2 on t1.id = t2.id;

MYSQL全连接查询不能用 mysql连接查询原理_结果集_04

 

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结果:

MYSQL全连接查询不能用 mysql连接查询原理_数据_05

因为被驱动表t1的字段a有索引,join过程用上了这个索引,因此这个语句的执行流程是这样的:

  1. 从表t2中读入一行数据 R;
  2. 从数据行R中,取出a字段到表t1里去查找;
  3. 取出表t1中满足条件的行,跟R组成一行,作为结果集的一部分;
  4. 重复执行步骤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结果如下:

MYSQL全连接查询不能用 mysql连接查询原理_数据_06

由于表t1的字段b上没有索引,所以每次都要把t1表中的每一行,拿出来进行对比, mysql采用的是Block Nested-Loop Join,Extra可以看到。该算法并没有将表t1全表扫描20次,它的执行流程为:

  1. 把表t2的数据读入线程内存join_buffer中,由于这个语句中写的是select *,因此是把整个表t2放入了内存;
  2. 扫描表t1,把表t1中的每一行取出来,跟join_buffer中的数据做对比,满足join条件的,作为结果集的一部分返回。

在这个过程中,对表t1和t2都做了一次全表扫描,因此总扫描次数为25,由于join_buffer是以无序数组的方式组织的,因此对表t1中的每一行,都要做20次判断,总共需要在内存中做的判断次数是:20*5=100次。

因为使用到了join_buffer,而join_buffer大小是有限的,由join_buffer_size设定,默认为256k。如果一次放不下t2中的所有字段,就会采用分段放的策略,执行策略如下:

  1. 假如t2表足够大,扫描表t2,顺序读取数据行放入join_buffer中,放完第n行join_buffer满了,继续第2步;
  2. 扫描表t1,把t1中的每一行取出来,跟join_buffer中的数据做对比,满足join条件的,作为结果集的一部分返回;
  3. 清空join_buffer;
  4. 继续扫描表t2,顺序读取最后的m行数据放入join_buffer中,继续执行第2步。

因此表t1可能会被扫描多次,在决定哪个表做驱动表的时候,应该是两个表按照各自的条件过滤,过滤完成之后,计算参与join的各个字段的总数据量数据量小的那个表,就是“小表”,应该作为驱动表

三个结论:

  1. 如果可以使用被驱动表的索引,join语句还是有其优势的;
  2. 不能使用被驱动表的索引,只能使用Block Nested-Loop Join算法,这样的语句就尽量不要使用;
  3. 在使用join的时候,应该让小表做驱动表。