mysql之join浅析
1.可以使用join吗?使用join有什么问题呢?-- >超过3个表不使用join,笛卡尔积问题 -->这些问题是怎么造成的呢?
如果可以使用 Index Nested-Loop Join 算法,也就是说可以用上被驱动表上的索引,是没问题的;
如果使用 Block Nested-Loop Join 算法,扫描行数就会过多。尤其是在大表上的 join 操作,这样可能要扫描被驱动表很多次,会占用大量的系统资源。这种 join 尽量不要用。
2.如果有两个大小不同的表做join,应该用哪个表做驱动表呢?-->小表 -->为什么要使用小表呢?
3.join 语句是怎么执行呢?
4.join语句怎么优化呢?
首先,创建两个表 t1 t2
CREATE TABLE `t2` (
`id` int(11) NOT NULL,
`a` int(11) DEFAULT NULL,
`b` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `a` (`a`)
) ENGINE=InnoDB; drop procedure idata;
delimiter ;;
create procedure idata()
begin
declare i int;
set i=1;
while(i<=1000)do
insert into t2 values(i, i, i);
set i=i+1;
end while;
end;;
delimiter ;
call idata(); create table t1 like t2;
insert into t1 (select * from t2 where id<=100)
//这两个表都有一个主键索引 id 和一个索引 a,字段 b 上无索引。存储过程 idata() 往表 t2 里插入了 1000 行数据,在表 t1 里插入的是 100 行数据
Index Nested-Loop Join (NLJ)
select * from t1 straight_join t2 on (t1.a=t2.a); //t1 是驱动表,t2 是被驱动表
被驱动表 t2 的字段 a 上有索引,join 过程用上了这个索引,因此这个语句的执行流程是这样的:
从表 t1 中读入一行数据 R;
从数据行 R 中,取出 a 字段到表 t2 里去查找;
取出表 t2 中满足条件的行,跟 R 组成一行,作为结果集的一部分;
重复执行步骤 1 到 3,直到表 t1 的末尾循环结束。
在这个流程中,总扫描行数200
如果不使用join,只能单表查询
select * from t1; //查出表 t1 的所有数据,这里有 100 行;
select * from t2 where a=$R.a;
在这个 join 语句执行过程中,驱动表是走全表扫描,而被驱动表是走树搜索。
假设被驱动表的行数是 M。每次在被驱动表查一行数据,要先搜索索引 a,再搜索主键索引。每次搜索一棵树近似复杂度是以 2 为底的 M 的对数,记为 log2M,所以在被驱动表上查一行的时间复杂度是 2*log2M。假设驱动表的行数是 N,执行过程就要扫描驱动表 N 行,然后对于每一行,到被驱动表上匹配一次。因此整个执行过程,近似复杂度是 N + N*2*log2M。显然,N 对扫描行数的影响更大,因此应该让小表来做驱动表。
这个结论的前提是“可以使用被驱动表的索引”。
如果被驱动表用不上索引,jion语句是怎么执行的呢?
Simple Nested-Loop Join
select * from t1 straight_join t2 on (t1.a=t2.b);
这样算来,这个 SQL 请求就要扫描表 t2 多达 100 次,总共扫描 100*1000=10 万行。
MySQL 也没有使用这个 Simple Nested-Loop Join 算法,而是使用了另一个叫作“Block Nested-Loop Join”的算法,简称 BNL。
Block Nested-Loop Join(BNL)
这时候,被驱动表上没有可用的索引,算法的流程是这样的:
把表 t1 的数据读入线程内存 join_buffer 中,由于我们这个语句中写的是 select *,因此是把整个表 t1 放入了内存;
扫描表 t2,把表 t2 中的每一行取出来,跟 join_buffer 中的数据做对比,满足 join 条件的,作为结果集的一部分返回。
对应地,这条 SQL 语句的 explain 结果如下所示:
在这个过程中,对表 t1 和 t2 都做了一次全表扫描,所以总的扫描行数是 1100。由于 join_buffer 是以无序数组的方式组织的,所以对表 t2 中的每一行,都要做 100 次判断,总共需要在内存中做的判断次数是:100*1000=10 万次。
前面使用 Simple Nested-Loop Join 算法进行查询,扫描行数也是 10 万行。因此,从时间复杂度上来说,这两个算法是一样的。但是,Block Nested-Loop Join 算法的这 10 万次判断是内存操作,速度上会快很多,性能也更好
在这种情况下,应该选择哪个表做驱动表?
假设小表的行数是 N,大表的行数是 M,那么在这个算法里:
两个表都做一次全表扫描,所以总的扫描行数是 M+N;
内存中的判断次数是 M*N。
这时调换M N 没有差别,所以这时候选择大表还是小表做驱动表,执行耗时是一样的。
但是如果表 t1 是一个大表,join_buffer 放不下怎么办呢?--->(join_buffer 的大小是由参数 join_buffer_size 设定的,默认值是 256k)
其实放不下时,就是分段放
这时执行过程就变成了
扫描表 t1,顺序读取数据行放入 join_buffer 中,放完第 88 行 join_buffer 满了,继续第 2 步;
扫描表 t2,把 t2 中的每一行取出来,跟 join_buffer 中的数据做对比,满足 join 条件的,作为结果集的一部分返回;
清空 join_buffer;
继续扫描表 t1,顺序读取最后的 12 行数据放入 join_buffer 中,继续执行第 2 步。
这时候由于表 t1 被分成了两次放入 join_buffer 中,导致表 t2 会被扫描两次。虽然分成两次放入 join_buffer,但是判断等值条件的次数还是不变的,依然是 (88+12)*1000=10 万次。
在这种情况下如何选择驱动表呢?
假设驱动表数据行是N,需要分成λ*N段,
所以,在这个算法的执行过程中:
扫描行数是 N+λ*N*M;
内存判断 N*M 次。
所以当N小一些的时候,整个算式结果会更小,所以应该让小表做驱动表
在决定哪个表做驱动表的时候,应该是两个表按照各自的条件过滤,过滤完成之后,计算参与 join 的各个字段的总数据量,数据量小的那个表,就是“小表”,应该作为驱动表。
比如
select * from t1 straight_join t2 on (t1.b=t2.b) where t2.id<=50;
select * from t2 straight_join t1 on (t1.b=t2.b) where t2.id<=50;
select t1.b,t2.* from t1 straight_join t2 on (t1.b=t2.b) where t2.id<=100;
select t1.b,t2.* from t2 straight_join t1 on (t1.b=t2.b) where t2.id<=100;
优化:思路是让 join 语句能够用上被驱动表上的索引,触发 BKA 算法(对 NLJ 算法的优化),提升查询性能(原表上加索引,或者使用有索引的临时表)
hash join :BNL算法join buffer中如果存储的是hash结构,内存判断就不是N*M次了,就变成了M次hash查找,MySQL目前不支持哈希join,这个优化思路可以在自己的业务端实现。
比如:
1.select * from t1;取得表 t1 的全部 100行数据,在业务端存入一个 hashMap中。
2.select * from t2 ; 获取表 t2 中的 1000行数据。把这 1000 行数据,一行一行地取到业务端,到 hash 结构的数据表中寻找匹配的数据。满足匹配的条件的这行数据,就作为结果集的一行。
ps:启用BKA算法
set optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
mysql之join浅析的更多相关文章
- Mysql查询优化器浅析
--Mysql查询优化器浅析 -----------------------------2014/06/11 1 定义 Mysql查询优化器的工作是为查询语句选择合适的执行路径.查询优化器的代码 ...
- MySQL Left Join,Right Join
魂屁,东西发这里了关于Left Join,Right Join的 在讲MySQL的Join语法前还是先回顾一下联结的语法,呵呵,其实连我自己都忘得差不多了,那就大家一起温习吧(如果内容有错误或有疑问, ...
- MySQL Full Join的实现
MySQL Full Join的实现 由于MySQL不支持FULL JOIN,以下是替代方法 left join + union(可去除反复数据)+ right join select * from ...
- mysql left join
MySQL左连接不同于简单连接.MySQL LEFT JOIN提供该表额外字段在左侧. 如果使用LEFT JOIN,得到的所有记录的匹配方式相同, 在左边表中得到的每个记录不匹配也会有一个额外的记录. ...
- MySQL的JOIN(一):用法
JOIN的含义就如英文单词"join"一样,连接两张表,大致分为内连接,外连接,右连接,左连接,自然连接.这里描述先甩出一张用烂了的图,然后插入测试数据. CREATE TABLE ...
- MySQL的JOIN(三):JOIN优化实践之内循环的次数
这篇博文讲述如何优化内循环的次数.内循环的次数受驱动表的记录数所影响,驱动表记录数越多,内循环就越多,连接效率就越低下,所以尽量用小表驱动大表.先插入测试数据. CREATE TABLE t1 ( i ...
- MySQL的JOIN(四):JOIN优化实践之快速匹配
这篇博文讲述如何优化扫描速度.我们通过MySQL的JOIN(二):JOIN原理得知了两张表的JOIN操作就是不断从驱动表中取出记录,然后查找出被驱动表中与之匹配的记录并连接.这个过程的实质就是查询操作 ...
- MySQL的JOIN(五):JOIN优化实践之排序
这篇博文讲述如何优化JOIN查询带有排序的情况.大致分为对连接属性排序和对非连接属性排序两种情况.插入测试数据. CREATE TABLE t1 ( id INT PRIMARY KEY AUTO_I ...
- Mysql Nested-Loop Join Algorithms
MySQL在多表之间执行join时,利用一种nested-loop algorithm 或者其变种:(嵌套循环) Nested-Loop Join Algorithm 一个简单的嵌套循环连 ...
随机推荐
- MySQL:互联网公司常用分库分表方案汇总!
一.数据库瓶颈 不管是IO瓶颈,还是CPU瓶颈,最终都会导致数据库的活跃连接数增加,进而逼近甚至达到数据库可承载活跃连接数的阈值.在业务Service来看就是,可用数据库连接少甚至无连接可用.接下来就 ...
- 数据库炸了----我就重启了一下啊(Communications link failure)
重启数据库后,数据库大部分时间连不上了:连续请求不会报错,请求间隔时间稍微长一点就会报错报错如图: com.mysql.cj.jdbc.exceptions.CommunicationsExcepti ...
- maven项目中 把依赖包打进jar包
在pom.xml文件中增加build配置 1 <build> 2 <plugins> 3 <plugin> 4 <artifactId>maven-as ...
- 【JAVA】笔记(6)--- toString方法;equals方法;finalize方法;package与import;内部类;
toString: 1.每创建一个类时,都要重写 toString 方法,这是敲代码的基本素养: 2.重写规则:简单明了: 3.String 类中也有toString方法(SUN公司写的): equa ...
- Java学习(十五)
下棋又被暴打 所以心态有点爆炸,学完习又去打云顶,忘记写博客了... 所以今天也有点水了,这下棋真是搞崩我心态了. Java学了函数重载 大概的要点在这 还有Web的学习,否定伪类. 如 p:not( ...
- [atAGC049F]Happy Sequence
定义$L=2\cdot 10^{5}$,$g(x)=\sum_{i=1}^{n}|b_{i}-x|-|a_{i}-x|$,则合法当且仅当$\forall 0\le x\le L,g(x)\ge 0$, ...
- [loj3180]天桥
考虑将所有交点作为关键点来建图跑最短路,但图上的关键点数量最坏为$o(nm)$,需要优化 当$s=0$且$g=n-1$的部分分,有以下结论: 1.对于一段天桥$([l,r],y)$,不会从$(r,y) ...
- [bzoj1863]皇帝的烦恼
二分枚举答案,假设是ans,考虑判定答案从前往后计算,算出每一个将军与第一个将军最少和最多有多少个相同的奖牌,贪心转移即可 1 #include<bits/stdc++.h> 2 usin ...
- 交叉编译环境的linaro-gdb可以用了,结果打开core文件,显示堆栈都是??
交叉编译环境的linaro-gdb可以用了,结果打开core文件,显示堆栈都是?? aarch64-linux-gun-gdb ./test core warning: /lib/libpthread ...
- 从零开始学Kotlin第六课
Kotlin调用java代码: 1.如果是内部工程的类,直接调用,如果是外部的工程项目按照java的方式将jar包导入进来. 2.实例化java对象 我们之前学java的时候实例化对象是这个样子的. ...