mysql 使用join进行多表关联查询

join类型

在一些报表统计或数据展示时候需要提取的数据分布在多个表中,这个时候需要进行join连表操作。join将两个或多个表当成不同的数据集合,然后进行集合取交集运算。比如有订单Order表记录用户id,如果像查询订单对应的用户信息,可以将Order和User表进行关联。

根据join结果集计算方式不同,join大致分为两种主要类型:

内连接

内连接(inner join)也称为等值连接,是最常用的Join方式。它只返回两个表中匹配的行,即两个表中具有相同值的列的行。比如 from order o inner join user u on o.uid=u.uid。

外连接

外连接分两种,左外连接和右外连接。两个区别于内连接地方是不仅包含两个表等值情况,返回左表中的所有行以及右表中与左表匹配的行。如果右表中没有匹配的行,则返回NULL。

如from order o left join user u on o.uid=u.uid。order.uid即使在user表不存在也会返回订单记录,只不过对应的用户信息为null。

右连接同理以右表数据为主返回右表所有数据。

还有一种全外连接,没有关联条件,查找两个表之间的所有关联和不关联的记录,无论它们是否有匹配条件。这种应该不常用,只有数据对比时候可能会用。

join原理

mysql在表join查询时候,使用嵌套循环方式(nested-loop algorithm)进行数据匹配。就是拿驱动表每一条和被驱动表所有数据依次进行匹配。这也很好理解。两个关联表相当于两个集合,然后求两个集合的交集。就是从一个集合依次拿出所有和另一个进行比较是否相等(是交集)。假如有t1、t2、t3三个表进行关联,t1上有range范围筛选,t1和t2关联有ref类型索引关联,执行过程大概这样

for each row in t1匹配范围数据 {
  for each row in t2匹配索引值 {
    for each row in t3 {
      t3满足连接条件记录
    }
  }
}

这样假设表A有M行记录,表B有N行记录。则需要M乘N次比较取交集。表B被扫描M*N次。这些都是磁盘全表扫描。显然如果M和N都比较大的时候会比较慢。

上面看到嵌套循环,外循环每行记录都会导致内循环多次执行。这里注意多表关联时候不是先计算前两个的关联结果然后结果再和后面的表进行关联。比如上面例子,不会有t1和t2的临时连接结果集。

Block Nested-Loop Join Algorithm

为了提高join的效率,mysql在嵌套循环比对时候进行了一定的修改优化。使用一个变种块嵌套循环(Block Nested-Loop Join Algorithm)。什么意思呢?这次我读取驱动表的时候不是每次读取一条了,我读取多条,存放到一个join buffer 块里。然后这样每次多条和被驱动表的数据进行比较,这样就将原来的多次磁盘读取比较转移到了内存比较。这样被驱动表全表扫描次数变成了:(M/buffer rows)*N。

一次读取的行数(buffer rows),由每行需要的数据大小和join_buffer_size大小两个因素决定。

join_buffer_size的大小默认是256KB

mysql> SELECT @@join_buffer_size;
+--------------------+
| @@join_buffer_size |
+--------------------+
|             262144 |
+--------------------+

从上面的计算公式不难看出,驱动表的行数越少,join_buffer_size越大,扫描被驱动表的次数越少。这也是为什么要使用小表驱动大表的原因。如果驱动表join数据可以一次放到join_buffer_size中则被驱动表只需要一次全表扫描。如果不能驱动表会被分成多次读取到join_buffer_size中,join_buffer_size会循环使用。

join buffer有以下几个特点:

1、当执行计划的type类型是ALL、index或range的时候会使用join buffer。

2、如果一个查询中有多个表进行连接操作, Join Buffer 只会为非常量表分配内存空间,即使这个表的类型是 ALL 或者 index。

3、连接操作的 Join Buffer 中只存储连接所需的列,而不是整个行。

4、一个join分配一个join buffer,如果一个查询有多个join可能会使用多个join buffer。

5、join buffer的分配在join执行前完成,查询结束后立即释放。

如果使用了join buffer,一般在explain的Extra列会有Using join buffer (Block Nested Loop)信息。

Index Nested-Loop Join

上面说的使用Block Nested-Loop Join算法,相对最初的嵌套循环使用join buffer提高了匹配的效率。但是当表数据很大时,仍然不太理想。在Block Nested-Loop Join算法中执行计划type最好是range。还有更好的ref和eq_ref没有说。也就是当join的列是索引列时。

注意这里说的索引列是指被驱动表,因为驱动表总是要进行一次全表扫描。但是这个时候去被驱动表找数据就不是全表扫描了,因为有索引,是直接在索引树上进行查找这就快多了。

在常规业务开发(非高并发)进行表join操作是非常常见的,在表连接时候尽量使用小表驱动大表,这里的小表是指条件过滤后参加join条件的数据。连接时在被驱动表连接字段上创建索引,也就是使用Index Nested-Loop Join。

join优化

Batched Key Access Joins(BKA)

Batched Key Access Joins从名字上也能看出来大致意思。key是索引意思,是对Index Nested-Loop Join的查询优化。 BKA要借助于MRR,先来说下MRR。

Multi-Range Read

当在某个辅助索引上进行范围查找时,通常在回表查询的过程中会发生大量的随机读。

比如下面的语句

select * from user where uname like '曹%';

uname列上建有索引,可以从uname索引上获取主键值,然后再回表根据主键逐条从聚簇索引上查询行数据。这个时候获取的主键是无序的,会造成大量的随机读。MRR(Multi-Range Read)优化就是为了解决这种场景,

MRR处理过程是在根据索引获取到主键后不是立即进行回表查询,而是先将主键值放入到一个read_rnd_buffer中,然后对其进行排序,最后再有序的去聚簇索引检索行数据。如果对应主键索引值足够密集,这样可以大大减少随机读。

read_rnd_buffer的大小由read_rnd_buffer_size参数来进行控制,默认大小是256kb。另外开启MMR需要在optimizer_switch中设置mrr=on。默认值是on。另外还有一个相关的参数mrr_cost_based,如果这个参数设置为off表示符合条件就使用mrr,如果为on则优化器会基于成本进行考虑是否使用mrr。该值默认是on 。

如果使用到了mrr,在explain的Extra列一般会显示:Using index condition; Using MRR。

看完了上面的MRR,这里回到使用索引join连表查询其实也存在上面的问题。回想Index Nested-Loop Join的查询执行过程,是每次一行行的去驱动表中去找数据,每次驱动表只被匹配一个值。这里Batched Key Access Joins就结合了Block Nested-Loop Join和MRR两个的优点,执行过程大致如下:

1、批量取驱动表的关联字段放入join buffer中

2、将join buffer中的关联列值,批量的发送给引擎在被驱动表上通过MRR以最优的方式进行索引树搜索匹配,获取到对应的主键(rowId)。

3、按检索到的rowId进行排序,顺序从被驱动表上获取关联数据。

BKA是对Index Nested-Loop Join的优化,被驱动表上要有对应的索引。并且被驱动表上会发生回表查询的情况。

开启BKA首先需要开启MRR,需要进行以下设置

mysql> SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';

你可能感兴趣的:(mysql学习记录,mysql,数据库)