join重复数据 tp5_MySQL 的 join 功能弱爆了吧?

ac028bedda584a4da8bc06f614940829.png

关于MySQL 的 join,大家一定了解过很多它的“轶事趣闻”,比如两表 join 要小表驱动大表,阿里开发者规范禁止三张表以上的 join 操作,MySQL 的 join 功能弱爆了等等。这些规范或者言论亦真亦假,时对时错,需要大家自己对 join 有深入的了解后才能清楚地理解。

下面,我们就来全面的了解一下 MySQL 的 join 操作。

正文

在日常数据库查询时,我们经常要对多表进行连表操作来一次性获得多个表合并后的数据,这是就要使用到数据库的 join 语法。join 是在数据领域中十分常见的将两个数据集进行合并的操作,如果大家了解的多的话,会发现 MySQL,Oracle,PostgreSQL 和 Spark 都支持该操作。本篇文章的主角是 MySQL,下文没有特别说明的话,就是以 MySQL 的 join 为主语。而 Oracle ,PostgreSQL 和 Spark 则可以算做将其吊打的大boss,其对 join 的算法优化和实现方式都要优于 MySQL。

MySQL 的 join 有诸多规则,可能稍有不慎,可能一个不好的 join 语句不仅会导致对某一张表的全表查询,还有可能会影响数据库的缓存,导致大部分热点数据都被替换出去,拖累整个数据库性能。

所以,业界针对 MySQL 的 join 总结了很多规范或者原则,比如说小表驱动大表和禁止三张表以上的 join 操作。下面我们会依次介绍 MySQL join 的算法,和 Oracle 和 Spark 的 join 实现对比,并在其中穿插解答为什么会形成上述的规范或者原则。

对于 join 操作的实现,大概有 Nested Loop Join (循环嵌套连接),Hash Join(散列连接) 和 Sort Merge Join(排序归并连接) 三种较为常见的算法,它们各有优缺点和适用条件,接下来我们会依次来介绍。

MySQL 中的 Nested Loop Join 实现

Nested Loop Join 是扫描驱动表,每读出一条记录,就根据 join 的关联字段上的索引去被驱动表中查询对应数据。它适用于被连接的数据子集较小的场景,它也是 MySQL join 的唯一算法实现,关于它的细节我们接下来会详细讲解。

MySQL 中有两个 Nested Loop Join 算法的变种,分别是 Index Nested-Loop Join 和 Block Nested-Loop Join。

Index Nested-Loop Join 算法

下面,我们先来初始化一下相关的表结构和数据

CREATE TABLE `t1` (
  `id` int(11) NOT NULL,
  `a` int(11) DEFAULT NULL,
  `b` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `a` (`a`)
) ENGINE=InnoDB;

delimiter ;;
# 定义存储过程来初始化t1
create procedure init_data()
begin
  declare i int;
  set i=1;
  while(i<=10000)do
    insert into t1 values(i, i, i);
    set i=i+1;
  end while;
end;;
delimiter ;
# 调用存储过来来初始化t1
call init_data();
# 创建并初始化t2
create table t2 like t1;
insert into t2 (select * from t1 where id<=500)

有上述命令可知,这两个表都有一个主键索引 id 和一个索引 a,字段 b 上无索引。存储过程 init_data 往表 t1 里插入了 10000 行数据,在表 t2 里插入的是 500 行数据。

为了避免 MySQL 优化器会自行选择表作为驱动表,影响分析 SQL 语句的执行过程,我们直接使用 straight_join 来让 MySQL 使用固定的连接表顺序进行查询,如下语句中,t1是驱动表,t2是被驱动表。

select * from t2 straight_join t1 on (t2.a=t1.a);

使用我们之前文章介绍的 explain 命令查看一下该语句的执行计划。

e2cf6880f91d1de9eb00584a57e08ebb.png

从上图可以看到,t1 表上的 a 字段是由索引的,join 过程中使用了该索引,因此该 SQL 语句的执行流程如下:

  • 从 t2 表中读取一行数据 L1;
  • 使用L1 的 a 字段,去 t1 表中作为条件进行查询;
  • 取出 t1 中满足条件的行, 跟 L1组成相应的行,成为结果集的一部分;
  • 重复执行,直到扫描完 t2 表。

这个流程我们就称之为 Index Nested-Loop Join,简称 NLJ,它对应的流程图如下所示。

5f6e7fc99cae96cb8c8e80b9a13cc587.png

需要注意的是,在第二步中,根据 a 字段去表t1中查询时,使用了索引,所以每次扫描只会扫描一行(从explain结果得出,根据不同的案例场景而变化)。

假设驱动表的行数是N,被驱动表的行数是 M。因为在这个 join 语句执行过程中,驱动表是走全表扫描,而被驱动表则使用了索引,并且驱动表中的每一行数据都要去被驱动表中进行索引查询,所以整个 join 过程的近似复杂度是 N2log2M。显然,N 对扫描行数的影响更大,因此这种情况下应该让小表来做驱动表。

当然,这一切的前提是 join 的关联字段是 a,并且 t1 表的 a 字段上有索引。

如果没有索引时,再用上图的执行流程时,每次到 t1 去匹配的时候,就要做一次全表扫描。这也导致整个过程的时间复杂度编程了 N * M,这是不可接受的。所以,当没有索引时,MySQL 使用 Block Nested-Loop Join 算法。

Block Nested-Loop Join

Block Nested-Loop Join的算法,简称 BNL,它是 MySQL 在被驱动表上无可用索引时使用的 join 算法,其具体流程如下所示:

  • 把表 t2 的数据读取当前线程的 join_buffer 中,在本篇文章的示例 SQL 没有在 t2 上做任何条件过滤,所以就是讲 t2 整张表 放入内存中;
  • 扫描表 t1,每取出一行数据,就跟 join_buffer 中的数据进行对比,满足 join 条件的,则放入结果集。

比如下面这条 SQL

select * from t2 straight_join t1 on (t2.b=t1.b);

这条语句的 explain 结果如下所示。可以看出

f1e7968e27672147fe796b087fc0f652.png

可以看出,这次 join 过程对 t1 和 t2 都做了一次全表扫描,并且将表 t2 中的 500 条数据全部放入内存 join_buffer 中,并且对于表 t1 中的每一行数据,都要去 join_buffer 中遍历一遍,都要做 500 次对比,所以一共要进行 500 * 10000 次内存对比操作,具体流程如下图所示。

f04f6cbfd18489d88f85fc8f0b18f55b.png

主要注意的是,第一步中,并不是将表 t2 中的所有数据都放入 join_buffer,而是根据具体的 SQL 语句,而放入不同行的数据和不同的字段。比如下面这条 join 语句则只会将表 t2 中符合 b >= 100 的数据的 b 字段存入 join_buffer。

select t2.b,t1.b from t2 straight_join t1 on (t2.b=t1.b) where t2.b >= 100;

join_buffer 并不是无限大的,由 join_buffer_size 控制,默认值为 256K。当要存入的数据过大时,就只有分段存储了,整个执行过程就变成了:

  • 扫描表 t2,将符合条件的数据行存入 join_buffer,因为其大小有限,存到100行时满了,则执行第二步;
  • 扫描表 t1,每取出一行数据,就跟 join_buffer 中的数据进行对比,满足 join 条件的,则放入结果集;
  • 清空 join_buffer;
  • 再次执行第一步,直到全部数据被扫描完,由于 t2 表中有 500行数据,所以一共重复了 5次

这个流程体现了该算法名称中 Block 的由来,分块去执行 join 操作。因为表 t2 的数据被分成了 5 次存入 join_buffer,导致表 t1 要被全表扫描 5次。

2bd4a859653f5c7ecc9267deea0aa625.png

如上所示,和表数据可以全部存入 join_buffer 相比,内存判断的次数没有变化,都是两张表行数的乘积,也就是 10000 * 500,但是被驱动表会被多次扫描,每多存入一次,被驱动表就要扫描一遍,影响了最终的执行效率。

基于上述两种算法,我们可以得出下面的结论,这也是网上大多数对 MySQL join 语句的规范。

  • 被驱动表上有索引,也就是可以使用Index Nested-Loop Join 算法时,可以使用 join 操作。
  • 无论是Index Nested-Loop Join 算法或者 Block Nested-Loop Join 都要使用小表做驱动表。

因为上述两个 join 算法的时间复杂度至少也和涉及表的行数成一阶关系,并且要花费大量的内存空间,所以阿里开发者规范所说的严格禁止三张表以上的 join 操作也是可以理解的了。

但是上述这两个算法只是 join 的算法之一,还有更加高效的 join 算法,比如 Hash Join 和 Sorted Merged join。可惜这两个算法 MySQL 的主流版本中目前都不提供,而 Oracle ,PostgreSQL 和 Spark 则都支持,这也是网上吐槽 MySQL 弱爆了的原因(MySQL 8.0 版本支持了 Hash join,但是8.0目前还不是主流版本)。

其实阿里开发者规范也是在从 Oracle 迁移到 MySQL 时,因为 MySQL 的 join 操作性能太差而定下的禁止三张表以上的 join 操作规定的 。

Hash Join 算法

Hash Join 是扫描驱动表,利用 join 的关联字段在内存中建立散列表,然后扫描被驱动表,每读出一行数据,并从散列表中找到与之对应数据。它是大数据集连接操时的常用方式,适用于驱动表的数据量较小,可以放入内存的场景,它对于没有索引的大表和并行查询的场景下能够提供最好的性能。可惜它只适用于等值连接的场景,比如 on a.id = where b.a_id。

还是上述两张表 join 的语句,其执行过程如下

59dd664e944597b1596accf0798b5be7.png
  • 将驱动表 t2 中符合条件的数据取出,对其每行的 join 字段值进行 hash 操作,然后存入内存中的散列表中;
  • 遍历被驱动表 t1,每取出一行符合条件的数据,也对其 join 字段值进行 hash 操作,拿结果到内存的散列表中查找匹配,如果找到,则成为结果集的一部分。

可以看出,该算法和 Block Nested-Loop Join 有类似之处,只不过是将无序的 Join Buffer 改为了散列表 hash table,从而让数据匹配不再需要将 join buffer 中的数据全部遍历一遍,而是直接通过 hash,以接近 O(1) 的时间复杂度获得匹配的行,这极大地提高了两张表的 join 速度。

不过由于 hash 的特性,该算法只能适用于等值连接的场景,其他的连接场景均无法使用该算法。

Sorted Merge Join 算法

Sort Merge Join 则是先根据 join 的关联字段将两张表排序(如果已经排序好了,比如字段上有索引则不需要再排序),然后在对两张表进行一次归并操作。如果两表已经被排过序,在执行排序合并连接时不需要再排序了,这时Merge Join的性能会优于Hash Join。Merge Join可适于于非等值Join(>,<,>=,<=,但是不包含!=,也即<>)。

需要注意的是,如果连接的字段已经有索引,也就说已经排好序的话,可以直接进行归并操作,但是如果连接的字段没有索引的话,则它的执行过程如下图所示。

334e41017e82d6467298295935a3a36c.png
  • 遍历表 t2,将符合条件的数据读取出来,按照连接字段 a 的值进行排序;
  • 遍历表 t1,将符合条件的数据读取出来,也按照连接字段 a 的值进行排序;
  • 将两个排序好的数据进行归并操作,得出结果集。

Sorted Merge Join 算法的主要时间消耗在于对两个表的排序操作,所以如果两个表已经按照连接字段排序过了,该算法甚至比 Hash Join 算法还要快。在一边情况下,该算法是比 Nested Loop Join 算法要快的。

下面,我们来总结一下上述三种算法的区别和优缺点。

7689a4a2ea3a768e65e8dd8d3f8c9d53.png

对于 Join 操作的理解

讲完了 Join 相关的算法,我们这里也聊一聊对于 join 操作的业务理解。

在业务不复杂的情况下,大多数join并不是无可替代。比如订单记录里一般只有订单用户的 user_id,返回信息时需要取得用户姓名,可能的实现方案有如下几种:

  1. 一次数据库操作,使用 join 操作,订单表和用户表进行 join,连同用户名一起返回;
  2. 两次数据库操作,分两次查询,第一次获得订单信息和 user_id,第二次根据 user_id 取姓名,使用代码程序进行信息合并;
  3. 使用冗余用户名称或者从 ES 等非关系数据库中读取。

上述方案都能解决数据聚合的问题,而且基于程序代码来处理,比数据库 join 更容易调试和优化,比如取用户姓名不从数据库中取,而是先从缓存中查找。

当然, join 操作也不是一无是处,所以技术都有其使用场景,上边这些方案或者规则都是互联网开发团队总结出来的,适用于高并发、轻写重读、分布式、业务逻辑简单的情况,这些场景一般对数据的一致性要求都不高,甚至允许脏读。

但是,在金融银行或者财务等企业应用场景,join 操作则是不可或缺的,这些应用一般都是低并发、频繁复杂数据写入、CPU密集而非IO密集,主要业务逻辑通过数据库处理甚至包含大量存储过程、对一致性与完整性要求很高的系统。

来源:MySQL 的 join 功能弱爆了?
作者:程序员历小冰
解析vCard 3.0:提取电子名片中的姓名 N:LastName;FirstName;;;END:VCARD和END:VCARD标记数据块的开始和结束。N字段存储结构化姓名(分号分隔)。FN字段为直接可读的显示名称。其他字段如TELEMAIL等保存联系信息。 阅读详情

相关推荐

【四足机器人项目实战】四足机器人运动学、动力学模型

系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 TODO:写完再整理 文章目录系列文章目录前言一、四足机器人实际模型的物理难点二、四足机器人运动学模型1.方法一:DH法建立运动学模型(1)运动学建模构型方法(2)运动学模型描述方法1、基于DH法四足机器人单腿运动学模型公式推导(0)DH法科普1)原理2)实现过程3)评价(1)四足机器人的建模规则、坐标系定义及实物参数确定1)标准Denavit-Hartenberg法建模规则2)单腿运动学模型坐标系定义示意图及简化模型3)

qq_35635374的博客 1万+

MySQLjoin 功能弱爆了?

大家好,我是历小冰,今天我们来学习和吐槽一下 MySQLJoin 功能。 关于MySQLjoin,大家一定了解过很多它的“轶事趣闻”,比如两表 join 要小表驱动大表,阿里开发者规范禁止三张表以上的 join 操作,MySQLjoin 功能弱爆了等等。这些规范或者言论亦真亦假,时对时错,需要大家自己对 join 有深入的了解后才能清楚地理解。 下面,我们就来全面的了解一下 MySQLjoin 操作。 正文 在日常数据库查询时,我们经常要对多表进连表操作来一次性获得多个表合并后的数

taylor的专栏 1687

CDD数据库文件制作(十一)——服务配置(0x19_DTC Code)

虽然选择Copy和Reference都可以加载DTC,但是如果我们在DTC库中有修改DTC,通过Copy的方式加载的DTC在DTC Table中不会跟着DTC库的修改而自动更新。通过Reference的方式加载的DTC可以自动更新。找到Fault Memory的DTC Table,鼠标放在DTC Table区域,右键点击选择Copy …根据需要(客户协议)勾选响应的19服务子功能。如何创建一个新的DTC code?按照字母与下方要素可做一一对应。首先在DTC库中新建DTC。支持10 01/10 03。

LOVE135149的博客 1875

MySQL 使用join连接查询效率低解决办法

最近研究公司的原来业务逻辑,发现使用联表查询,多层嵌套都把我搞蒙了,就想着使用join优化 进过测试,业务逻辑没有问题,结果也相同,但是发现一个问题,使用join连接查询,查询效率会变慢,查询用时会比原来增加一个量级,这还是在数据量不大的情况下,后期大量数据,肯定会影响查询效率 尝试解决办法: 1、过滤查询条件,查看是否有影响索引的条件出现,发现没有,此路不通 2、黔驴技穷了,直接上百度,发现一大堆解决方案,奈何自己水平有限,对于算法,优化等很小白,暂时不考虑 3、无意中,发现一个方法,通

qq_30062771的博客 1596

Mysql的 left/right join 添加where条件

Mysql的 left/right join 添加where条件

Mao_yafeng的博客 5819

Mysql join效率_mysql join 性能原理

MRR在说明Batched Key Access Join前,首先介绍下MySQL 5.6的新特性mrr——multi rangeread。这个特性根据rowid顺序地,批量地读取记录,从而提升数据库的整体性能。看下面的SQL语句的执计划:mysql> explain select * from orders-> where o_orderdate >= '1993-08-01...

weixin_30574361的博客 895

数据库系列之MySQLJoin语句优化问题

最近使用MySQL 8.0.25版本时候遇到一个SQL问题,两张表做等值Join操作执很慢,当对Join连接字段添加索引优化后,执效率反而变得更差,其中的原因值得分析。因此本文介绍下MySQL中常见的Join算法,并对比使用不同Join算法时候的性能情况。

牧羊人的方向 4127

mysql 表连接_MySQL中基本的多表连接查询教程

一、多表连接类型1. 笛卡尔积(交叉连接) 在MySQL中可以为CROSS JOIN或者省略CROSS即JOIN,或者使用',' 如:由于其返回的结果为被连接的两个数据表的乘积,因此当有WHERE, ON或USING条件的时候一般不建议使用,因为当数据表项目太多的时候,会非常慢。一般使用LEFT [OUTER] JOIN或者RIGHT [OUTER] JOIN2. 内连接INNER JOIN...

weixin_39568597的博客 464

为什么 MySQL 不推荐使用 join

1. 对于 mysql,不推荐使用子查询和 join 是因为本身 join 的效率就是硬伤,一旦数据量很大效率就很难保证,强烈推荐分别根据索引 单表取数据,然后在程序里面做 join,merge 数据。   2. 子查询就更别用了,效率太差,执子查询时,MYSQL 需要创建临时表,查询完毕后再删除这些临时表,所以,子查询的速度会 受到一定的影响,这里多了一个创建和销毁临时表的过程。   3. 如果是 JOIN 的话,它是走嵌套查询的。小表驱动大表,且通过索引字段进关联。如果表记录比较少的话,还是

weixin_50580200的博客 552

left join on多条件深度理解

left join on多条件深度理解 核心:理解左连接的原理! 左连接不管怎么样,左表都是完整返回的 当只有一个条件a.id=b.id的时候: 左连接就是相当于左边一条数据,匹配右边表的所有,满足on后面的第一个条件a.id=b.id的进返回 当有两个条件的时候a.id=b.id and a.age>100(当第二个条件进左表筛选时) 就是左边这张表只有a.age>100的,才会参与右表的每匹配(但是a.age<100的也会返回,只不过age<100的是不可能匹配到

cxywangshun的博客 3万+

SQL语法—left join on多条件查询深度理解

SQL语法—left join on多条件查询深度理解

m0_65473615的博客 9774

left join 多条件匹配问题

left join时多个条件匹配问题

weixin_58625114的博客 1643

MySQL inner join 加多个条件

SELECT * FROM ((表1 INNER JOIN 表2 ON 表1.字段号=表2.字段号) INNER JOIN 表3 ON 表1.字段号=表3.字段号) INNER JOIN 表4 ON Member.字段号=表4.字段号。SELECT * FROM (表1 INNER JOIN 表2 ON 表1.字段号=表2.字段号) INNER JOIN 表3 ON 表1.字段号=表3.字段号。SELECT * FROM 表1 INNER JOIN 表2 ON 表1.字段号=表2.字段号。

weixin_40572778的博客 3848

Mysql join加多条件与where的区别

在连表操作的时候,其实是先进了2表的全连接(笛卡尔积,也就是所有能组合的情况a.rowCount*b.rowCount),然后根据on后面的条件进筛选,最后如果是左连接或者右连接,再补全左表或者右表的数据。第一反应写错了,仔细检查没有问题。on后面条件筛选是对2张表生成的全连接(笛卡尔积)临时表进的筛选,无论on后面的条件是否满足都会返回左表的所有数据,不符合条件的右表的值都为null。inner join有点不一样,它是两张表取交集,最终的结果是符合所有条件的值,所以on后面的条件可以生效。

lucky_fd的博客 958

mysql left join 后边的on条件

结论: left join 为保证左表所有 因此 on里的条件只对右表起作用,控制左表的条件写到这里也没用 原理: on条件是在生成临时表时使用的条件,它不管on中的条件是否为真,都会返回左边表中的记录。 where条件是在临时表生成好后,再对临时表进过滤的条件。这时已经没有left join的含义(必须返回左边表的记录)了,条件不为真的就全部过滤掉。 https://www.cnblogs.com/nxzblogs/p/13447652.html https://www.cnblogs.com/can

文盲青年的博客 1859

left join 连表问题解析:on后多条件无效 & where与on的区别

在项目中用到多表联合查询,发现2个现象,今天解决这2个疑问: 1、left join连接2张表,on后的条件第一个生效,用and连接的其他条件不生效; 2、一旦加上where,则显示的结果等同于inner join; 先写结论: 过滤条件放在: where后面:是先连接然生成临时查询结果,然后再筛选 on后面:先根据条件过滤筛选,再连 生成临时查询结果 table1 left joi...

郭芳的博客 1万+

SQL语法——left join on 多条件

https://blog.csdn.net/minixuezhen/article/details/79763263

mengxiao12345678的博客 1933

mysqlJOIN用法详解-附带查询示例

在 SQL 中,JOIN是用于将多个表中的数据连接在一起的操作。它通过指定连接条件将两个或多个表中符合条件的组合起来,产生一个新的结果集。SQL 中常见的 JOIN 类型包括和。

厚积薄发. 9471

thinkphp3, thinkphp5原生sql, 分页, MySQL关键字 keywords

thinkphp3 执原生sql M()->query($sql); thinkphp5原生sql use think\Db; Db:query($sql, [$param, ...]); Db::execute($sql, [$param, ...]); Thinkphp3 public function coupon_list() { $li...

fareast_mzh的博客 1135

MySQL数据库设计规范

目录1. 规范背景与目的 2. 设计规范 2.1 数据库设计 2.1.1 库名 2.1.2 表结构 2.1.3 列数据类型优化 2.1.4 索引设计 2.1.5 分库分...

代码技巧 599

Xilinx Pg149 FIR滤波器用户手册

这是一份xilinx的FIR ip用户指南,包含了如何使用Vivado FIR Compiler的步骤、实例、技巧和常见问题解答。建议读者仔细阅读这份文档,以便深入理解和掌握FIR滤波器在Vivado中的设计与实现方法。

gru神经网络电池soc预测.zip

基于MATLAB编程,用gru神经网络进电池SOC预测,电池soc是时间系列了数据,GRU进比一般神经网络更适合,代码完整,包含数据,有注释,方便扩展应用1,如有疑问,不会运,可以私信,2,需要创新,或者修改可以扫描二维码联系博主,3,本科及本科以上可以下载应用或者扩展,4,内容不完全匹配要求或需求,可以联系博主扩展。

上一篇: bapi sap 创建物料_SAP物料主数据的构成目录
下一篇: 航嘉电源维修法图解_OPPO R15 漏电不开机维修案例
浮在水里的朔子
博客等级 码龄7年 28粉丝 92原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值