Mysql连接join查询的原理

  • Post author:
  • Post category:mysql




基本描述

MySQL的连接查询对于每一个使用MySQL数据库的开发者来说都是耳熟能详的东西,包含了内连接inner join和外连接outer join,外连接又分为左(外)链接left join 和右(外)连接right join。通过它实现将两个或者多个表中符合条件的数据进行组合返回结果集。

以两个表为例:第一个需要查询的表A表称之为“驱动表”,第二个表B表称之为“被驱动表”。

内连接即就两个表数据的笛卡尔积(两个表中无论谁作为驱动表,记录得到的笛卡尔积也一定是一样的,所以在内连接中驱动表和被驱动表是可以互换的,只是在这里方便说明,设定A表为驱动表,B表为被驱动表),带上查询条件以后,如果驱动表A表中的记录在被驱动表B表中找不到对应记录,则该记录会被丢弃,不会加入到最终的结果集中。

外连接划分成左或者右(外)连接,来源于驱动表的不同,左(外)连接即意味着左边的表为驱动表,右(外)连接亦然。

查询条件:

一般情况下,我们把只涉及到单表的过滤条件放在where子句中,把涉及到两表的过滤条件放在ON子句中,同时一般将ON子句中的过滤条件称为连接条件。

where子句中无论是内外连接,不符合where过滤条件的结果都会被丢弃,不会加入到返回的结果集中。

ON子句中对于外连接,被驱动表中如果们无法找到匹配ON过滤条件的记录,则会填充一条各个字段为null占位的记录到结果集中,并且ON子句是专门为外连接驱动表的记录在被驱动表中找不到对应的记录这个场景下提出的,在内连接中MySQL会将其和where子句同样对待,所以在内连接查询时不要求一定要写ON子句。



原理

MySQL在处理连接时是以

嵌套循环连接算法

的方式来实现的。

嵌套循环连接,顾名思义就是一层层嵌套的循环判断,伪代码如下:

for each row in t1{
	for each row in t2{
		for each row in t3{
		// 匹配判断
		...
		}
	}
}

多表连接的时候,先从驱动表t1中进行单表查询得到结果集r1,然后拿r1结果集中的

每条数据挨个作为条件

去和被驱动表t2中进行单表查询得到的结果集r2进行匹配,最终得到匹配结果r12,然后使用r12结果集中的

每条数据挨个作为条件

去和被驱动表t3中进行单表查询得到的结果集r3进行匹配,得到最终结果集并返回,当有很多个表连接查询的时候就是不断重复上面的步骤,简单来说就是拿上次(连接)查询的结果作为驱动表,来和被驱动表进行单表查询得到的结果集进行匹配。

上边强调了

每条数据挨个作为条件

这几个字眼,为什么要强调这几个字眼呢,就要说到嵌套循环连接算法的问题了,在连接查询过程中,驱动表只访问一次,但是被驱动表需要被访问多次,我们知道MySQL的数据都是持久化存储在硬盘上的,而要匹配驱动表和被驱动表中的数据是发生在内存中的,那么使用每条数据挨个作为条件和被驱动表单表查询结果进行匹配时都要经历一波IO操作,当驱动表和被驱动表中的数据量很大时,这个查询就会相当耗时和消耗资源,并且从伪代码可以看出,N多个表连接查询时,算法时间复杂度就会成指数增长,这也是阿里巴巴开发手册中要求join不能超过3个表的原因之一。



优化

  1. 使用索引

    上边讲到连接查询是使用嵌套循环连接算法来实现的,驱动表只访问一次,被驱动表需要被访问多次,在没有使用索引的情况下,驱动表和被驱动表每次单表查询都会是全表扫描,那么既然连接查询的算法是固定的,就从每次查询的速度优化入手,这时候就必须提到索引,索引是为了提高查询效率的数据结构,如果在一般情况下每次驱动表和被驱动表的单表查询是都使用到索引,查询效率就会大幅提高(不一般的情况下就是查询优化器觉得索引查询的效率低于全表扫描,就会放弃索引查询,这时候是不使用索引的)。

  2. 基于块的嵌套循环连接

    上面的基本嵌套循环连接查询和使用索引都是被驱动表中得到的结果集每次只和一条驱动表中记录进行匹配,那么在这个过程中驱动表中有多少条数据,就要访问多少次被驱动表进行单表查询,访问被驱动表就是要将被驱动表中的记录从硬盘加载到内存中,然后在内存中和驱动表中的数据进行匹配,然后将被驱动表中的记录从内存中移除,然后再从驱动表中拿出下一条记录重复这个步骤,由此可见这种方式会带来大量的IO操作,所以MySQL的设计者就提出了基于块的嵌套循环连接来减少被驱动表的访问次数,减少对应的IO操作。

    基于块的嵌套循环连接引入了“join buffer”概念,即在进行连接查询前申请一块固定大小的内存,将驱动表中结果集中若干条记录存储在join buffer中,然后扫描被驱动表,将被驱动中的每一条记录一次性和join buffer中的多条记录进行比较匹配,从而减少被驱动表的访问次数。

    join buffer 的大小可以通过系统变量“join_buffer_size”进行配置,默认大小为256KB,最小可以设置为128KB,既然是要一次性和驱动表中的多条记录进行匹配,那么最理想的情况就是驱动表中符合条件的所有数据都存在于join buffer中,那么只需要访问一次被驱动表就可以完成所有的连接查询,所以在优化连接查询时,可以适当增大join_buffer_size的大小,使得驱动表中的记录可以尽可能多的存储到join buffer中,从而减少被驱动表的访问次数。同时在这个过程中尽量不要使用select *进行查询,指定确实需要的字段,减少存储到join buffer中每条数据的大小,这样在固定大小的join_buffer中可以存放更多的驱动表记录,减少被驱动表的访问次数。



版权声明:本文为yifan_lion原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。