MySQL之连接原理

背景

在使用MySQL数据库过程中,left join 基本是必用的语法,不过 join 会导致性能变慢,MySQL是如何将多张表的数据结合到一起的,了解join的运作机制,有利于写出更好性能的 SQL 。

先创建两张表,并分别放3条数据:

create table t1(m1 int, n1 char(1));
create table t2(m2 int, n2 char(1));
insert into t1 values(1 , 'a'), (2, 'b'), (3, 'c');
insert into t2 values(1 , 'b'), (2, 'c'), (3, 'd');
复制代码

笛卡尔积

t1和t2分开来查,分别都是3条。

image.png

image.png

两表合并一起查的时候,发现由6条变成了9条。

image.png

多张表关联查询的时候,就是把每张表中的记录拿出来进行匹配,然后进行组合,产生最终的结果给到客户端,

由于上面的sql,t1、t2一起查询,并没有匹配条件,因此会将两表的所有记录进行交叉组合,最终形成笛卡尔积,即:

  1. t1 的 的 1 和 t2的 1、2、3 进行组合,形成了三条记录。
  2. t1 的 的 2 和 t2的 1、2、3 进行组合,形成了三条记录。
  3. t1 的 的 3 和 t2的 1、2、3 进行组合,形成了三条记录。

最终变成了9条。

image.png

过滤条件

当使用表连接时,如果不增加任何限制条件,那么产生的笛卡尔积会非常巨大,三张表,每张表100条记录,三张表连接产生的记录就是 100 x 100 x 100 = 1000000 条,因此需要增加过滤条件,排除掉不必要的组合。

示例SQLselect * from t1, t2 where t1.m1 > 1 and t1.m1 = t2.m2 and t2.n2 < 'd'

过滤条件分为两种:

  1. 单表过滤条件: t1.m1 > 1 和 t2.n2 < 'd' 都是单表过滤条件。

  2. 两表过滤条件: t1.m1 = t2.m2 就是两表过滤条件。

连接过程

示例SQL的连接过程如下:

  1. 使用t1作为驱动表,因为t1没有任何索引,因此全表扫描 m1 > 1 的数据。

  2. 从t1中查到的每条记录,都需要去t2(被驱动表)中进行一次匹配,由于t2也没有索引,因此也需要进行全表扫描,找到匹配 t2.m2 = t1.m1 以及 t2.n2 < 'd' 的数据。

image.png

image.png

这个过程中,驱动表只需要被访问一次,但是被驱动表可能需要被访问多次,驱动表每获取到一条记录都会去被驱动表扫描一次匹配的记录联合起来。

内连接和外连接

连接分为内连接和外连接,二者的区别是,前者如果在被驱动表中找不到匹配的记录,则不会加入到最终的结果集中。

过滤条件

  1. where 子句条件,不符合where条件的记录最终都不会加入结果集中。
  2. on 子句条件,如果是内连接,效果和where等价,不符合则不会加入结果集中,如果是外连接,驱动表的记录还是会加入结果集中,被驱动表的则不会加入结果集,此时被驱动表的列以NULL值填充。

外连接

外连接又分为左外连接和右外连接,前者是选择左侧表作为驱动表,后者选择右侧表作为驱动表。

左(外)连接语法

select * from t1 left [outer] join t2 on 连接条件 [where 普通过滤条件]

t1有4,t2没有,使用左连接, t1的数据依然被查出来了,但是t2没有的则以null值填充。

image.png

右(外)连接语法

select * from t1 right [outer] join t2 on 连接条件 [where 普通过滤条件]

t2有5,t1没有,使用左连接, t5的数据依然被查出来了,但是t1没有的则以null值填充。

image.png

左外连接或者右外连接中的外是可以被省略称呼的,即称为左连接或右连接,outer 也可以从sql中被省略掉。

内连接语法

select * from t1 [inner | cross] join t2 [on 连接条件] [where 普通过滤条件];

内连接的写法比较多,以下3种处于等价状态:

  1. select * from t1 join t2
  2. select * from t1 inner join t2
  3. select * from t1 cross join t2

一般建议使用第二种方式。

可以看到内连接不符合on条件的没有被加入到结果集中:

image.png

内连接由于on子句的过滤特性,只要不满足一律过滤,因此可以不区分驱动表与被驱动表,但是外连接的on子句会将驱动表的放入结果集,因此根据要求,外连接要区分好驱动表和非驱动表。

连接原理

嵌套循环连接(Nested-Loop Join)

连接就是一张表作为驱动表,去匹配另一张表,步骤如下:

  1. 选取驱动表,使用与驱动表相关的过滤条件,选取代价最低的单标访问方法读取驱动表中的数据。
  2. 对步骤1中得到的每条记录都去被驱动表中根据连接条件匹配相应的记录。
  3. 如果还有第三张表,那么步骤2得到的结果成为驱动表,第三张表作为被驱动表,重复匹配过程。

三张表的嵌套循环连接,用代码表示就是三层for循环:

for item in t1
    for item in t2
        for item in t3
复制代码

使用索引加快连接速度

在嵌套循环连接中,驱动表每条记录都要去被驱动表中全表扫描一次,但是通过在被驱动表中建立索引,可以减少扫描的代价。

一般情况下,也推荐使用索引进行连接查询,如果是主键或者唯一不允许为空的情况的下,连接代价是常数级别的,即const,在连接中称为 eq_ref ,这种性能最高,

如果业务场景中,正好只需要用到索引中的列数据,尽量在查询列表和条件列表中只使用索引列,减少回表带来的代价。

Join Buffer

由于驱动表的每条记录都需要去访问一次被驱动表,这在表记录中非常大的情况下并且是无法使用索引的连接过程中,

会导致前面被驱动表刚从磁盘加载到内存中的数据,由于后续的数据加载,被释放掉以腾出地方,驱动表频繁的对被驱动表的访问,将导致大量的IO,性能会非常的差,

因此MySQL新加了一个Join Buffer,将驱动表的多条数据存入一个连接缓冲区中,扫描被驱动表时,将被驱动表的记录与Join Buffer中的多条记录进行匹配,以此减少被驱动表的IO操作,

如果Join Buffer足够大,可以存放驱动表的所有内容,那么只需要访问一次被驱动表就可以完后连接操作。

Join Buffer默认大小是256KB,可以通过启动选项或者环境变量 join_buffer_size 进行设置,在无法使用索引的情况下进行连接,机器本身内存又比较大的情况,可以调大这个参数,使的减少对被驱动表的访问,

Join Buffer会存放查询列表中的列和过滤条件中的列的数据,因此建议不要用 select * ,节约空间使的Join Buffer可以存放更多的记录。

Guess you like

Origin juejin.im/post/7076324298385784840