背景

这几天同事写报表,sql语句如下

select * from `sail_marketing`.`mk_coupon_log` a left join `cp0`.`coupon` c on c.code_id = a.coupon_code;

查询出来的结果花了60多秒

数据背景

  • mk_coupon_log表数据 9368

  • coupon表数据37735

  • mk_coupon_log表的coupon_code字段有索引

分析过程

分析原始sql语句

explain

select * from `sail_marketing`.`mk_coupon_log` a left join `cp0`.`coupon` c on c.code_id = a.coupon_code;

type都是ALL,即全表查询,没有用到索引,其中Extra列出现 Using join buffer (Block Nested Loop),从字面意思理解用到了缓存跟循环

其中a表也没用到coupon_code列的索引

一般情况下,第一行称为驱动表,第二行称为被驱动表,从上图可知,a表示驱动表,c表示被驱动表

果断给coupon表的code_id字段加上索引

 

增加索引之后查询时间0.136秒,分析其sql语句

explain

select * from `sail_marketing`.`mk_coupon_log` a left join `cp0`.`coupon` c on c.code_id = a.coupon_code;

从上图可知,a表驱动c表,c表用到刚加的索引,以及ref到a表的索引,查询效率上急速提高

试试反向left join,查询结果为1.362秒,分析sql

explain select * from `cp0`.`coupon` c  left join `sail_marketing`.`mk_coupon_log` a on c.code_id = a.coupon_code

c表驱动a表,用到了a表的索引,也ref到c表的索引,由上可知,都有索引的情况下说明小表驱动大表效率更高

删掉coupon表刚加的索引,还原到原始状况

将left去掉,使用 join连接两个表

select * from `sail_marketing`.`mk_coupon_log` a join `cp0`.`coupon` c on a.coupon_code =c.code_id

查询结果为0.138秒,分析sql语句

explain

select * from `sail_marketing`.`mk_coupon_log` a join `cp0`.`coupon` c on a.coupon_code =c.code_id

去掉left后,发现跟初始的语句explain解析结果不一样了,c表变成驱动表,a表变成被驱动表,查询效率上也急速提升

使用

select * from `sail_marketing`.`mk_coupon_log` a, `cp0`.`coupon` c where c.code_id = a.coupon_code

语句跟上述语句效果一样

从上述结果来看,

1:如果使用left join语句,则left join 前的表设置为驱动表,left join 后的表设置为被驱动表,被驱动表如果没加索引,效率会非常低,Extra列出现using join buffer(block Nested-loop)

2:使用join on,where,inner join on等连接表语句,则mysql会自动优化, 将加索引的表设置为被驱动表,未加索引的表设为驱动表

3:两个表都存在索引的情况下,小表驱动大表查询效果更好

初始sql语句执行效率超过60秒,初步判断原因在于using join buffer(Block nested loop), 查询多方资料,从insidemysql公众号上查到关于join的分析

join原理

 

join原理参考下图,R是驱动表,S是被驱动表

上图表示R表joinS表, 其中Fetch阶段是指当内表(被驱动表)关联的列是辅助索引时,但是需要访问表中的数据,那么这时就需要再访问主键索引才能得到数据的过程,不论表的存储引擎是InnoDB存储引擎还是MyISAM,这都是无法避免的,只是MyISAM的回表速度要快点,因为其辅助索引存放的就是指向记录的指针,而InnoDB存储引擎是索引组织表,需要再次通过索引查找才能定位数据。

接着计算两张表Join的成本,这里有下列几种概念:

  • 外表(驱动表)的扫描次数,记为O。通常外表的扫描次数都是1,即Join时扫描一次驱动表的数据即可

  • 内表(被驱动表)的扫描次数,记为I。根据不同Join算法,内表的扫描次数不同

  • 读取表的记录数,记为R。根据不同Join算法,读取记录的数量可能不同

  • Join的比较次数,记为M。根据不同Join算法,比较次数不同

  • 回表的读取记录的数,记为F。若Join的是辅助索引,可能需要回表取得最终的数据

通常来说join的实现由以下三种

1:simple nested-loop join(SNLJ)

算法相当简单,即驱动表(外表r)中的每一条记录与被驱动表(内表s)的记录进行判断

从驱动表中取出R1匹配S表所有列,然后R2,R3,直到将R表中的所有数据匹配完,然后合并数据

可以看到这种方式效率是非常低的,以上述a表数据8000条,c表数据30000条计算,则S*R =2亿多次,开销统计如下

2: index Nested-loop join(INLJ)

我们通常建议在被驱动表建索引,原因就是使用到了index Nested-loop join原理

驱动表中的每条记录通过被驱动表的索引进行访问,因为索引查询的成本是比较固定的,故mysql优化器都倾向于使用记录数少的表作为驱动表(外表)

这种算法在链接查询的时候,驱动表会根据关联字段的索引进行查找,当在索引上找到了符合的值,再回表进行查询,也就是只有当匹配到索引以后才会进行回表。至于驱动表的选择,MySQL优化器一般情况下是会选择记录数少的作为驱动表,但是当SQL特别复杂的时候不排除会出现错误选择。

上表Smatch表示通过索引找到匹配的记录数量。同时可以发现,通过索引可以大幅降低内表(被驱动表)的Join的比较次数,每次比较1条外表的记录,其实就是一次indexlookup(索引查找),而每次index lookup的成本就是树的高度,即IndexHeight。

根据开始的示例,可知如果被驱动表加索引,效率是非常高的,但是开始的示例中加的索引不是主键索引,所以还得进行一次回表查询,辅助索引先查主键索引,然后根据主键索引再查询数据结果, 故如果被驱动表的索引是主键索引,则效率会更高,上述我未尝试,有兴趣的可以自己尝试下

3:block Nested-loop join

通常情况下,mysql会先判断被驱动表是否有索引,如果没有索引的话也肯定不是simple Nested-loop join方式,毕竟代价太大,效率太低,故出现第三种方式

Simple Nested-Loop Join算法的缺点在于其对于内表的扫描次数太多,从而导致扫描的记录太过庞大。Block Nested-Loop Join算法较Simple Nested-Loop Join的改进就在于可以减少内表的扫描次数,仅需扫描内表一次。

Block Nested-Loop Join对比Simple Nested-Loop Join多了一个中间处理的过程,也就是join buffer,使用join buffer将驱动表的查询JOIN相关列都给缓冲到了JOIN BUFFER当中,然后批量与非驱动表进行比较,这也来实现的话,可以将多次比较合并到一次,降低了非驱动表的访问频率。也就是只需要访问一次S表。这样来说的话,就不会出现多次访问非驱动表的情况了,也只有这种情况下才会访问join buffer。

在MySQL当中,我们可以通过参数join_buffer_size来设置join buffer的值,然后再进行操作。默认情况下join_buffer_size=256K,在查找的时候MySQL会将所有的需要的列缓存到join buffer当中,包括select的列,而不是仅仅只缓存关联列。在一个有N个JOIN关联的SQL当中会在执行时候分配N-1个join buffer。

整体效率比较 INLJ > BNLJ > SNLJ

上述分析可知,原始的sql用到了Block Nested-loop 原理,虽然比Simple Nested-loop效率高,但整体而言还是很慢的,

由一个场景分析Mysql的join原理的更多相关文章

  1. MYSQL索引结构原理、性能分析与优化

    [转]MYSQL索引结构原理.性能分析与优化 第一部分:基础知识 索引 官方介绍索引是帮助MySQL高效获取数据的数据结构.笔者理解索引相当于一本书的目录,通过目录就知道要的资料在哪里, 不用一页一页 ...

  2. 深入浅出分析MySQL MyISAM与INNODB索引原理、优缺点、主程面试常问问题详解

    本文浅显的分析了MySQL索引的原理及针对主程面试的一些问题,对各种资料进行了分析总结,分享给大家,希望祝大家早上走上属于自己的"成金之路". 学习知识最好的方式是带着问题去研究所 ...

  3. 深入浅出分析MySQL MyISAM与INNODB索引原理、优缺点分析

    本文浅显的分析了MySQL索引的原理及针对主程面试的一些问题,对各种资料进行了分析总结,分享给大家,希望祝大家早上走上属于自己的"成金之路". 学习知识最好的方式是带着问题去研究所 ...

  4. 【转】由浅入深探究mysql索引结构原理、性能分析与优化

    摘要: 第一部分:基础知识 第二部分:MYISAM和INNODB索引结构 1.简单介绍B-tree B+ tree树 2.MyisAM索引结构 3.Annode索引结构 4.MyisAM索引与Inno ...

  5. 一个最不可思议的MySQL死锁分析

    1    死锁问题背景    1 1.1    一个不可思议的死锁    1 1.1.1    初步分析    3 1.2    如何阅读死锁日志    3 2    死锁原因深入剖析    4 2. ...

  6. MySQL死锁系列-常见加锁场景分析

    在上一篇文章<锁的类型以及加锁原理>主要总结了 MySQL 锁的类型和模式以及基本的加锁原理,今天我们就从原理走向实战,分析常见 SQL 语句的加锁场景.了解了这几种场景,相信小伙伴们也能 ...

  7. <转>一个最不可思议的MySQL死锁分析

    1 死锁问题背景 1 1.1 一个不可思议的死锁 1 1.1.1 初步分析 3 1.2 如何阅读死锁日志 3 2 死锁原因深入剖析 4 2.1 Delete操作的加锁逻辑 4 2.2 死锁预防策略 5 ...

  8. MySQL的JOIN(二):JOIN原理

    表连接算法 Nested Loop Join(NLJ)算法: 首先介绍一种基础算法:NLJ,嵌套循环算法.循环外层是驱动表,循坏内层是被驱动表.驱动表会驱动被驱动表进行连接操作.首先驱动表找到第一条记 ...

  9. left join 原理分析

    left join 原理分析 [转贴 2006-11-15 16:19:50]     字号:大 中 小 案例分析 user表:  id   | name --------- 1   | libk   ...

随机推荐

  1. 分区恢复和NTFS文件恢复试验

    一.实验室名称:主楼实验室A2-412                  二.实验项目名称:分区恢复和NTFS文件恢复试验 三.实验学时:6学时 四.实验原理: 借助fdisk.diskgen软件对磁 ...

  2. SOCKET, TCP/UDP, HTTP, FTP 浅析

    SOCKET, TCP/UDP, HTTP, FTP (一)TCP/UDP,SOCKET,HTTP,FTP简析   TCP/IP是个协议组,可分为三个层次:网络层.传输层和应用层: 网络层:IP协议. ...

  3. 关于调试php的socket服务端中遇到的问题及解决办法

    今天终于把socket的服务端解决了,期间遇到了很多问题呢~ 1.用cmd运行php的问题: 2.socket_create()函数未定义问题: 3.查看端口的问题. 以下逐一说说解决办法: 1.在c ...

  4. Shrio03 Authenticator、配置多个Realm、SecurityManager认证策略

    1 Authenticator 简介 1.1 层次结构图 1.2 作用 职责是验证用户帐号,是ShiroAPI中身份验证核心的入口点:接口中声明的authenticate方法就是用来实现认证逻辑的. ...

  5. PropertySheet

    ---------------------------------include----------------------------------- E:\OpenSourceGraph\OSG_i ...

  6. Mybatis 实用篇(四)返回值类型

    Mybatis 实用篇(四)返回值类型 一.返回 List.Map List<User> getUsers(); <select id="getUsers" re ...

  7. 3 python之基础概要

    一: 与用户交互 1 什么事与用户交互 程序等待用户输入一些数据,程序执行完毕之后为用户反馈信息 2 为什么程序要与用户交互 为了让计算机像人一样和用户沟通 3 如何用: 在python3中:inpu ...

  8. 怎样才能做好SNS社区网站

    顺应历史潮流,大批的网络社区SNS(Social Networking Services)发展起来. SNS旨在构建一个网络上的人际关系网,让公众足不出户即可实现社交意愿.SNS满足了人的交流欲望,成 ...

  9. sqlserver怎么将excel表的数据导入到数据库中

    在数据库初始阶段,我们有些数据在EXCEL中做好之后,需要将EXCEL对应列名(导入后对应数据库表的字段名),对应sheet(改名为导入数据库之后的表名)导入指定数据库, 相当于导入一张表的整个数据. ...

  10. C++继承-重载-多态-虚函数

    C++ 继承 基类 & 派生类 一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数.定义一个派生类,我们使用一个类派生列表来指定基类.类派生列表以一个或多个基类命名,形式如下: ...