MySQL - Left Join和Inner Join的效率对比,以及优化

2023-11-07

最近在写代码的时候,遇到了需要多表连接的一个问题,初始sql类似于:

select * 
from a 
left join b on a.id = b.aid 
left join c on c.bid = b.id 
left join d on d.cid = c.id

这样的多个left join组合,总觉得这种写法是有问题的,后续使用inner join发现速度要比left join快一些

一、left join为什么会比 inner join 慢

(一)关于逻辑运算量

关于left join的概念,大家是都知道的(返回左边全部记录,右表不满足匹配条件的记录对应行返回null),那么单纯的对比逻辑运算量的话,inner join 是只需要返回两个表的交集部分,left join多返回了一部分左表没有返回的数据。

(二)MySQL Nested-Loop Join算法

Nested-Loop Join Algorithms

这个算法是mysql默认的连接算法,类似于程序代码中的三个嵌套循环:

Table   Join Type
t1      range
t2      ref
t3      ALL

for each row in t1 matching range {
  for each row in t2 matching reference key {
    for each row in t3 {
      if row satisfies join conditions, send to client
    }
  }
}

从算法上来看,根据mysql文档,inner join在连接的时候,mysql会自动选择较小的表来作为驱动表,从而达到减少循环次数的目的。我们在使用left join表的时候,默认是使用左表作为驱动表,那么此时左表的大小是我们来控制的,如果控制不当,左表比较大,那么自然循环次数也会变多,效率会下降。

这段代码很简单,这里假设有三张表,t1, t2, t3,这段代码,分别会展现出explain计划里的range, ref和ALL,表现在SQL执行计划层里,t3就会进行一次全表扫描,其中驱动表就是伪代码里的t1表MySQL会自动选择结果集最小的表作为驱动表,作为算法分析,这样选择驱动表确实是消耗最小的办法。那么这里还提到了,通过缩小驱动表结果集进行连接优化,那么根据这个算法来看,结果集较小的驱动表确实可以使循环次数减少

select c.* 
from hotel_info_original c
left join hotel_info_collection h on c.hotel_type = h.hotel_type 
and c.hotel_id =h.hotel_id
where h.hotel_id is null

这个sql是用来查询出c表中有h表中无的记录,所以想到了用left join的特性(返回左边全部记录,右表不满足匹配条件的记录对应行返回null)来满足需求,不料这个查询非常慢。先来看查询计划:

rows代表这个步骤相对上一步结果的每一行需要扫描的行数,可以看到这个sql需要扫描的行数为35773*8134,非常大的一个数字。本来c和h表的记录条数分别为40000+和10000+,这几乎是两个表做笛卡尔积的开销了(select * from c,h)

Nested Loop Join 实际上就是通过驱动表的结果集作为循环基础数据,然后一条一条的通过该结果集中的数据作为过滤条件到下一个表中查询数据,然后合并结果。如果还有第三个参与 Join,则再通过前两个表的 Join 结果集作为循环基础数据,再一次通过循环查询条件到第三个表中查询数据,如此往复,基本上MySQL采用的是最容易理解的算法来实现join。所以驱动表的选择非常重要,驱动表的数据小可以显著降低扫描的行数。

那么为什么一般情况下join的效率要高于left join很多?

一般情况下参与联合查询的两张表都会一大一小,如果是join,在没有其他过滤条件的情况下MySQL会选择小表作为驱动表,但是left join一般用作大表去join小表,而left join本身的特性决定了MySQL会用大表去做驱动表,这样下来效率就差了不少,如果我把上面那个sql改成

select c.* 
from hotel_info_original c
join hotel_info_collection h on c.hotel_type = h.hotel_type 
and c.hotel_id = h.hotel_id

查询计划如下: 

很明显,MySQL选择了小表作为驱动表,再配合(hotel_id,hotel_type)上的索引瞬间降低了好多个数量级

如果where条件中含有右表的非空条件(除开is null),则left join语句等同于join语句,可直接改写成join语句

Block Nested-Loop Join Algorithm

当然了,MySQL自己在这个算法基础上,演进出了Block Nested-Loop join算法,其实基本上和上面的算法没有区别,伪代码如下:

for each row in t1 matching range {
  for each row in t2 matching reference key {
    store used columns from t1, t2 in join buffer
    if buffer is full {
      for each row in t3 {
        for each t1, t2 combination in join buffer {
          if row satisfies join conditions,
          send to client
        }
      }
      empty buffer
    }
  }
}

if buffer is not empty {
  for each row in t3 {
    for each t1, t2 combination in join buffer {
      if row satisfies join conditions,
      send to client
    }
  }
}

这个算法将外层循环的数据缓存

在join buffer中,内层循环中的表回合buffer中的数据进行对比,从而减少循环次数,这样便可以提高效率。官网上有个example,我有点没有看明白:如果有10行被缓存到了buffer里,这10行被传给了内层循环,内层循环的所有行都会和buffer中的这10行进行对比。原文是这样的:

For example, if 10 rows are read into a buffer and the buffer is passed to the next inner loop, each row read in the inner loop can be compared against all 10 rows in the buffer

如果S指的是t1, t2组合在缓存中的大小,C是这些组合在buffer中的数量,那么t3表被扫描的次数应该是:

(S * C)/join_buffer_size + 1

根据这个算式,join_buffer_size越大,扫描的次数越小,如果join_buffer_size到了能缓存所有之前的行组合,那么这时就是性能最好的时候,之后再增大也就没有什么效果了

根据这两方面的对比,left join明显被秒成渣,但是我们的实际业务却经常需要使用left join,一切还是要以实际业务为主,所以大家还是仁者见仁智者见智的选择吧。博主这里因为业务并不是很需要left join,所以果断选择使用inner join来连接表

二、关于 left join的优化

根据上面咱们的对比,基本可以总结出来一些简单的优化方案 

1、left join选择小表作为驱动表(这部分基本是大家的共识)

2、如果左表比较大,并且业务要求驱动表必须是左表,那么我们可以通过where条件语句,使得左表被过滤的小一些,主要原理和第一条类似

3、关联字段给索引,因为在mysql的嵌套循环算法中,是通过关联字段进行关联,并查询的,所以给关联字段索引很必要

4、如果sql里面有排序,请给排序字段加上索引,不然会造成排序使用全表扫描

5、如果where条件中含有右表的非空条件(除开is null),则left join语句等同于join语句,可直接改写成join语句

6、根据文档,MySQL能更高效地在声明具有相同类型和尺寸的列上使用索引。所以把表与表之间的关联字段给上encoding和collation(决定字符比较的规则)全部改成统一的类型

7、右表的条件列一定要加上索引(主键、唯一索引、前缀索引等),最好能够使type达到range及以上(ref,eq_ref,const,system)

可参考

mysql 多个left join 怎么优化? - OSCHINA - 中文开源技术交流社区

从一个MySQL left join优化的例子加深对查询计划的理解 - 移动互联网的浪潮来了,我能捞点虾兵蟹将吗 - ITeye博客

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MySQL - Left Join和Inner Join的效率对比,以及优化 的相关文章

  • 用 Java 创建 PDF 的缩略图

    我正在寻找一个 Java 库 它可以获取 PDF 并从第一页创建缩略 图 PNG 我已经看过 JPedal 但其疯狂的许可费完全令人望而却步 我目前正在使用 iText 来操作 PDF 文件 但我相信它不会生成缩略图 我可以在命令行上使用
  • 在intellij中为java启用ssl调试

    从我的问题开始 上一期尝试通过 tls ssl 发送 java 邮件 https stackoverflow com questions 39259578 javamail gmail issue ready to start tls th
  • Android JSONObject内部多个JSONObject的解析

    我有一个来自服务器的 JSON 字符串 它看起来像这样 categories 0 term id 247 name Content Curation 1 term id 50 name Content Marketing 2 term id
  • Laravel - 带有 join 和 concat 的查询生成器

    我试图从用户表中提取与 users groups 数据透视表中某个组匹配的所有用户 顺便说一句 我使用的是来自 Cartalyst 的 Sentry 2 这可以让所有用户的名字和姓氏连接起来 User select DB raw CONCA
  • 使用 pdfbox 1.8.8 进行视觉签名

    我正在尝试生成带有视觉签名和 pdfbox 的 PDF 我有两个流 似乎 pdfbox 只能处理文件 如果没有三个临时文件 我就无法使其工作 我可以看到从here https github com apache pdfbox blob b7
  • Hibernate OneToMany 列表中的重复结果

    我已将 1 N 关系与 OneToMany 列表映射 但当我访问该列表时 由于 OUTER JOIN 结果会重复 映射如下所示 Entity public class Programmer ElementCollection fetch F
  • Firemonkey 移动应用程序和远程 MySQL

    我正在使用 Delphi XE5 并且我已经创建了基本的 Firemonkey 移动应用程序 Delphi 我想连接到远程 MySQL 数据库 我应该使用哪一个数据库管道 FireDAC DBExpress 等 您可以使用标准 TSQLQu
  • 当点击 JButton 连接到服务器时,程序冻结

    我正在使用 Java 中的 TCP 进行简单的 1v1 私人聊天 目前 每当我使用 JButtons 时都会遇到问题 这是我第一次尝试使用 JButtons 和 ActionListeners 所以我不能 100 确定这里发生了什么 我有两
  • Apache:配置应用程序侦听器时出错

    当我尝试运行该应用程序时 我遇到了这个问题 警告 SetContextPropertiesRule Context 设置属性 源 到 org eclipse jst j2ee server CongressApp 没有找到匹配的 财产 20
  • 序列化的 lambda 且没有serialVersionUID?

    我正在尝试了解 Java 及其最新版本的序列化如何工作 我正在尝试像这样序列化 lambda Runnable r Runnable Serializable gt System out println This is a test 但我注
  • 使用 jaxb 编组时使用派生类

    我有一个具有公共基类的对象列表 我尝试使用 jaxb 将其序列化为 XML 我希望在编组时使用派生类的注释 但我在实现这一点时遇到了麻烦 import java util Arrays import java util List impor
  • 使用 BigInteger 进行 Karatsuba 乘法

    我首先使用 long 编写了 Karasuba 算法的代码 我认为它工作得很好 使用相同的逻辑 我将代码转换为 BigInteger 但由于某些原因 它给出了 StackOverflowError 我不明白为什么 请帮忙 EDIT1 长时间
  • Jhipster 实体子生成器:如何创建 liquibase DELTA 变更日志?

    jhipster version 5 3 4 我正在评估 JHipster 并测试它如何处理数据库 DDL 修改 在我的测试中 我创建了一个 Jhipster 项目 然后运行jhipster import jdl model jh JHip
  • 如何调试没有错误消息的错误?

    如何调试没有错误消息的错误 当加载 PHP 页面时 我在 Firefox 中收到此错误 The connection to the server was reset while the page was loading 除了看起来是 Apa
  • 匹配 MySQL 中单词/后缀的相似/变体

    我如何匹配 MySQL 中单词的变体 例如搜索 accountancy 应匹配 accountant accountants accounting 等 我使用共享主机 因此无法向 MySQL 添加任何功能 例如 levenshtein 我想
  • 如何选择非“唯一”行

    我有下表 我必须从中获取非唯一行 id idA infos 0 201 1899 1 205 1955 2 207 1955 3 201 1959 我想获取该列的所有行infos 具有相同的idA至少有两行的值 上表的查询输出必须是 inf
  • Javassist注释问题

    我正在尝试使用 javassist 生成我的实体类 一切都很顺利 直到我将 GenerationValue 注释添加到 Id 字段 Id 注释工作正常 但当我添加 GeneeratedValue 时 出现异常 这是我的代码 ClassPoo
  • SQL 查询中的外语/重音字符

    我正在使用 Java 和 Spring 的 JdbcTemplate 类在 Java 中构建一个 SQL 查询来查询 Postgres 数据库 但是 我在执行包含外来 重音字符的查询时遇到问题 例如 修剪后的 代码 JdbcTemplate
  • SAX:如何获取元素的内容

    我在理解使用 SAX 解析 XML 结构时遇到了一些困难 假设有以下 XML
  • ORA-02289: 序列不存在,hibernbate 中出错

    ORA 02289 序列不存在 hibernbate 中出错 在 Oracle 中 您无法自动生成值 您应该创建一个序列 我们称之为 VEHICLE SEQ 然后你应该把这个注释放在你的 id 上 GeneratedValue strate

随机推荐

  • JavaSE基础语法之 String 类

    目录 前言 一 常用方法 一 字符串构造 二 String 对象的比较 1 比较是否引用同一个对象 2 boolean equals Object anObject 方法 按照字典序比较 3 int compareTo String str
  • 图的深度优先遍历

    一 图遍历介绍 所谓图的遍历 即是对结点的访问 一个图有那么多个结点 如何遍历这些结点 需要特定策略 一般有两种访问策略 1 深度优先遍历 2 广度优先遍历 二 深度优先遍历基本思想 图的深度优先搜索 Depth First Search
  • hive-sql入门-实践摘记-持续更新

    Hive SQL记录 DQL Data QueryLanguage 只读权限 select DML Data manipulation language 数据操作语句 insert delete update DDL Data Defini
  • 【idea】IDEA全局搜索Jar包中内容(转载)

    转载 https blog csdn net qq 26012495 article details 109694133
  • ChatGPT研究分享:插件模式的利与弊

    目录 1 插件的实现方式 1 1 Toolformer 1 2 OpenAI插件文档 1 3 个人感想 2 一些有意思的点 2 1 知识和价值观 2 2 算法的研究方向 OpenAI近期公开了GPT 4 除了各方面性能的大幅度提升 最大的惊
  • shell中单引号、双引号的区别

    原文地址 http blog sina com cn s blog 5f5716580100l9k1 html hard quote 单引号 关闭所有的meta 什么是meta 自己查吧 soft quote 双引号 关闭大部分的meta
  • mysql中对视图的一系列操作

    一 知识讲解 1 数据库只储存视图的定义 不储存视图所对应的数据 2 视图可以进行插入 查询 删除和更新数据等操作 但是with check option 会对 where 子句中的条件 对其所进行的操作进行检查 3 当视图依赖多个基本表时
  • sqli-labs-master第15、16关

    前言 在第九关我们已经了解过GET方式的盲注 我们今天一起探讨下POST方式的盲注 第15关 http 192 168 89 134 sqli labs master Less 15 我们在尝试了 联合查询和报错注入方式后无果 我们初步判断
  • 调用存储过程并且使用返回值的基本方法

    调用存储过程并且使用返回值的基本方法 一直没有找到一种好的方法来引用有返回值的存储过程的方法 使我在添加数据中走了不少的弯路 最近 在查阅了大量的资料之后 终于在微软的一个实例中找到了一种良好的方法 首先编写好一有返回值的存储过程 crea
  • 宋浩-概率论与数理统计笔记

    有需要pdf版本的可以留言 没有更新假设检验部分 如有需要也可以更新
  • 技术博客能为你面试带来什么样的好处?

    作为一个老程序员 我看过的简历 面试过的程序员非常多 我直接说我的观点 写技术博客对面试中来说 有用 如果你有一个和岗位相关的的博客 Github 在我看来 这些能显得你这个人爱学习 爱总结分享 大厂里 很重视的一件事就是分享 分享经验 分
  • (18)语义分割--paddle--EISeg自动标注软件的使用和自己数据集的测试

    1 主要参考 1 使用过程 建议先看一下下面博主的视频 eiseg简单教学 哔哩哔哩 bilibili 2 软件使用 主要参考 百度飞浆EISeg高效交互式标注分割软件的使用教程 Leonard2021的博客 CSDN博客 安装eiseg
  • redis命令行操作五种数据类型

    这里写目录标题 1 redis有关key的操作命令 2 redis中关于string类型数据的操作命令 3 redis中关于list类型数据的操作命令 单key 多valu有序 4 redis中关于set类型数据的操作命令 单key 多va
  • 高数【连续、间断点】--猴博士爱讲课

    第二课 连续 间断点 函数连续不连续是要看区间的 1 3 证明f x 在某点连续 例一 试证明 f x
  • 蓝桥杯真题31日冲刺国一

    大家好 我是泡泡 接下来几天每天都有复习 目录 今日练习专题 一丶成绩统计 二丶既约分数 三丶最优包含 复习专题 一丶空间 二丶等差数列 三丶回文日期 四丶青蛙跳杯子 今日练习专题 一丶成绩统计 题目链接 成绩统计 蓝桥云课 lanqiao
  • 机器学习中如何选择分类器

    在机器学习中 分类器作用是在标记好类别的训练数据基础上判断一个新的观察样本所属的类别 分类器依据学习的方式可以分为非监督学习和监督学习 非监督学习顾名思义指的是给予分类器学习的样本但没有相对应类别标签 主要是寻找未标记数据中的隐藏结构 监督
  • C#实现的根据年月日计算星期几的函数

    算法如下 基姆拉尔森计算公式 W d 2 m 3 m 1 5 y y 4 y 100 y 400 mod 7 在公式中d表示日期中的日数 m表示月份数 y表示年数 注意 在公式中有个与其他公式不同的地方 把一月和二月看成是上一年的十三月和十
  • pandas 报警告:A value is trying to be set on a copy of a slice from a DataFrame

    pandas 报警告 A value is trying to be set on a copy of a slice from a DataFrame 我在抽取了原来DataFrame数据的几列后 对抽取后的数据进行赋值操作时弹出这个警告
  • 提取分割单引号 ‘ ‘ 之间的内容且不重复分割单引号 python

    分割两个单引号之间的内容 且不重复分割已使用的单引号 废话少说 直接上干货 import re result re findall string 将string替换为你需要分割的部分 示例代码 string jmp qword ptr ri
  • MySQL - Left Join和Inner Join的效率对比,以及优化

    最近在写代码的时候 遇到了需要多表连接的一个问题 初始sql类似于 select from a left join b on a id b aid left join c on c bid b id left join d on d cid