mysql jion 实现原理_MySQL-join的实现原理、优化及NLJ算法

案例分析:

selectc.*

fromhotel_info_original cleft joinhotel_info_collection honc.hotel_type=h.hotel_typeandc.hotel_id=h.hotel_idwhereh.hotel_idis null

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

43fb4e725541f75a8ecc80840a600c52.png

rows代表这个步骤相对上一步结果的每一行需要扫描的行数,可以看到这个sql需要扫描的行数为35773*8134,非常大的一个数字。

在EXPLAIN结果中,第一行出现的表就是驱动表。

NLJ 算法

即 Nested Loop Join,就是扫描一个表(外表,也叫驱动表),每读到一条记录,就根据 join 字段上的索引去另一张表(内表)里查找。内表(一般是带索引的表)被外表(也叫驱动表,一般为小表,不仅相对其他表为小表,而且记录数的绝对值也小,不要求有索引)驱动,外表返回的每一行都要在内表中检索与其匹配的行,因此整个返回的结果集不能太大(大于 1 万不适合)。

驱动表:就是在嵌套循环连接和哈希连接中,用来最先获得数据,并以此表的数据为依据,逐步获得其他表的数据,直至最终查询到所有满足条件的数据的第一个表。驱动表不一定是表,有可能是数据集,即由某个表中满足条件的数据行,组成子集合后,再以此子集合作为连接其他表的数据来源。这个子集合,才是真正的驱动表,有时候为了简洁,直接将最先按照条件或得子集合的那张表叫做驱动表。我们常说,驱动表一定是小表,指的是根据条件获得的子集合一定要小,而不是说实体表本身一定要小,大表如果获得的子集合小,一样可以简称这个大表为驱动表。

如果有三个及以上的表,则会先使用 NLJ 算法得到一、二个表的结果集,并将该结果集作为外层数据,遍历结果集到后第三个表中查询数据。

一个简单的嵌套循环联接(NLJ)算法,循环从第一个表中依次读取行,取到每行再到联接的下一个表中循环匹配。这个过程会重复多次直到剩余的表都被联接了。假设表t1、t2、t3用下面的联接类型进行联接:

Table Join Type

t1 range

t2 ref

t3 ALL

如果使用的是简单NLJ算法,那么联接的过程像这样:

for each row int1 matching range {for each row int2 matching reference key {for each row int3 {if row satisfies joinconditions,

send to client

}

}

}

因为NLJ算法是通过外循环的行去匹配内循环的行,所以内循环的表会被扫描多次。

由此可知道,on a.id = b.aid 代表着驱动表无法使用此索引,是给被驱动表用的。

BLJ 算法

即 Block Nested-Loop Join,是MySQL 自己创建的方式。将指定的外层键对应的被驱动表缓存起来以提高性能。

Join操作使用内存(join_buffer_size):应用程序经常会出现一些两表(或多表)Join的操作需求,MySQL在完成某些 Join 需求的时候(all/index join),为了减少参与Join的“被驱动表”的读取次数以提高性能,需要使用到 Join Buffer 来协助完成 Join操作(具体 Join 实现算法请参考:MySQL中的 Join基本实现原理)。当 Join Buffer太小,MySQL不会将该 Buffer存入磁盘文件,而是先将Join Buffer中的结果集与需要 Join 的表进行 Join操作,然后清空 Join Buffer中的数据,继续将剩余的结果集写入此 Buffer中,如此往复。这势必会造成被驱动表需要被多次读取,成倍增加 IO访问,降低效率。

for each row int1 matching range {for each row in t2 matching reference key{

store used columnsfrom t1, t2 in joinbufferif buffer is full{for each row int3 {for each t1, t2 combination in joinbuffer {if row satisfies joinconditions,

sendtoclient

}

}

empty buffer

}

}

}if buffer is notempty {for each row int3 {for each t1, t2 combination in joinbuffer {if row satisfies joinconditions,

sendtoclient

}

}

}

对上面的过程解释如下:

1. 将t1、t2的联接结果放到缓冲区,直到缓冲区满为止;

2. 遍历t3,内部再循环缓冲区,并找到匹配的行,发送到客户端;

3. 清空缓冲区;

4. 重复上面步骤,直至缓冲区不满;

5. 处理缓冲区中剩余的数据,重复步骤2。

设S是每次存储t1、t2组合的大小,C是组合的数量,则t3被扫描的次数为:

(S * C)/join_buffer_size + 1

由此可见,随着join_buffer_size的增大,t3被扫描的次数会较少,如果join_buffer_size足够大,大到可以容纳所有t1和t2联接产生的数据,t3只会被扫描1次。

实例1:

mysql> show create tablec;+-------+---------------------------------------------------------------------------------------------------------------------+

| Table | Create Table |

+-------+---------------------------------------------------------------------------------------------------------------------+

| c | CREATE TABLE`c` (

`id`int(11) NOT NULL,

`name`varchar(100) DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8 |

+-------+---------------------------------------------------------------------------------------------------------------------+

1 row in set (0.00sec)

mysql> show create tabled;+-------+-------------------------------------------------------------------------------------------------------------------------------------------------+

| Table | Create Table |

+-------+-------------------------------------------------------------------------------------------------------------------------------------------------+

| d | CREATE TABLE`d` (

`id`int(11) NOT NULL,

`score`int(11) DEFAULT NULL,

`stuid`int(11) DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8 |

+-------+-------------------------------------------------------------------------------------------------------------------------------------------------+

1 row in set (0.00sec)

mysql> explain select c.id,d.score from c,d where c.id=d.stuid;+----+-------------+-------+------+---------------+------+---------+------+------+--------------------------------+

| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |

+----+-------------+-------+------+---------------+------+---------+------+------+--------------------------------+

| 1 | SIMPLE | c | ALL | NULL | NULL | NULL | NULL | 42 | |

| 1 | SIMPLE | d | ALL | NULL | NULL | NULL | NULL | 61 | Using where; Using join buffer |

+----+-------------+-------+------+---------------+------+---------+------+------+--------------------------------+

2 rows in set (0.00 sec)

MySQL 会根据条件选用不同的执行策略。比如说在上面的 d 和 c 表中,如果按照当前的 c 和 d 的结构,执行 explain 之后,是 c 驱动 d 表,因为 c 表较小。

那么如果在c的id上加一个index之后,mysql就会采用d驱动c表了。

【因为此时,在Nested Loop Join算法中,内部循环可以使用c表上的索引,加速执行c表的查询。内部查询每加快一点,对整个join来说都是效率上比较大的提升】

mysql> alter table c add index(id);

Query OK,0 rows affected (0.94sec)

Records:0 Duplicates: 0 Warnings: 0mysql> explain select c.id,d.score from c,d where c.id=d.stuid;+----+-------------+-------+------+---------------+------+---------+--------------+------+-------------+

| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |

+----+-------------+-------+------+---------------+------+---------+--------------+------+-------------+

| 1 | SIMPLE | d | ALL | NULL | NULL | NULL | NULL | 61 | |

| 1 | SIMPLE | c | ref | id | id | 4 | test.d.stuid | 1 | Using index |

+----+-------------+-------+------+---------------+------+---------+--------------+------+-------------+

2 rows in set (0.00 sec)

实例2:

表结构:

create table`user_group` (

`user_id` int(11) NOT NULL,

`group_id`int(11) not null,

`user_type`int(11) not null,

`gmt_create`datetime not null,

`gmt_modified`datetime not null,

`status`varchar(16) not null,key `idx_user_group_uid` (`user_id`)

) engine=innodb default charset=utf8;create table`group_message` (

`id`int(11) not nullauto_increment,

`gmt_create`datetime not null,

`gmt_modified`datetime not null,

`group_id`int(11) not null,

`user_id` int(11) not null,

`author`varchar(32) not null,

`subject`varchar(128) not null,primary key(`id`),key `idx_group_message_author_subject` (`author`,`subject`(16)),key`idx_group_message_author` (`author`),key `idx_group_message_gid_uid` (`group_id`,`user_id`)

) engine=innodb auto_increment=97 default charset=utf8;create table`group_message_content` (

`group_msg_id`int(11) not null,

`content`text NOT NULL,KEY`group_message_content_msg_id` (`group_msg_id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

查询:

explainselectm.subject msg_subject,

c.content msg_contentfromuser_group g,

group_message m,

group_message_content cwhereg.user_id = 1

andm.group_id=g.group_idandc.group_msg_id= m.id\G

结果:

*************************** 1. row ***************************id:1select_type: SIMPLEtable: g

type: ref

possible_keys: user_group_gid_ind,user_group_uid_ind,user_group_gid_uid_indkey: user_group_uid_ind

key_len:4ref: const

rows:2Extra:*************************** 2. row ***************************id:1select_type: SIMPLEtable: m

type: ref

possible_keys:PRIMARY,idx_group_message_gid_uidkey: idx_group_message_gid_uid

key_len:4ref: example.g.group_id

rows:3Extra:*************************** 3. row ***************************id:1select_type: SIMPLEtable: c

type: ref

possible_keys: idx_group_message_content_msg_idkey: idx_group_message_content_msg_id

key_len:4ref: example.m.id

rows:2Extra:

MySQL Query Optimizer 选择了 user_group 作为驱动表,首先利用我们传入的条件 user_id 通过 该表上面的索引 user_group_uid_ind 来进行 const 条件的索引 ref 查找,然后以 user_group 表中过滤出来的结果集的 group_id 字段作为查询条件,对 group_message 循环查询,然后再通过 user_group 和 group_message 两个表的结果集中的 group_message 的 id 作为条件 与 group_message_content 的 group_msg_id 比较进行循环查询,才得到最终的结果。没啥特别的,后一个引用前一个的结果集作为条件。

如果去掉 group_message_content 上面的 idx_group_message_content_msg_id 这个索引,然后再看看会是什么效果:

*************************** 1. row ***************************id:1select_type: SIMPLEtable: g

type: ref

possible_keys: idx_user_group_uidkey: idx_user_group_uid

key_len:4ref: const

rows:2Extra:*************************** 2. row ***************************id:1select_type: SIMPLEtable: m

type: ref

possible_keys:PRIMARY,idx_group_message_gid_uidkey: idx_group_message_gid_uid

key_len:4ref: example.g.group_id

rows:3Extra:*************************** 3. row ***************************id:1select_type: SIMPLEtable: c

type:ALLpossible_keys:NULL

key: NULLkey_len:NULLref:NULLrows:96Extra: Usingwhere; Using join buffer

我们看到不仅仅 group_message_content 表的访问从 ref 变成了 ALL,此外,在最后一行的 Extra信息从没有任何内容变成为 Using where; Using join buffer,也就是说,对于从 ref 变成 ALL 很容易理解,没有可以使用的索引的索引了嘛,当然得进行全表扫描了,Using where 也是因为变成全表扫描之后,我们需要取得的 content 字段只能通过对表中的数据进行 where 过滤才能取得,但是后面出现的 Using join buffer 是一个啥呢?

我们知道,MySQL 中有一个供我们设置的参数 join_buffer_size ,这里实际上就是使用到了通过该参数所设置的 Buffer 区域。那为啥之前的执行计划中没有用到呢?

实际上,Join Buffer 只有当我们的 Join 类型为 ALL(如示例中),index,rang 或者是 index_merge 的时候 才能够使用,所以,在我们去掉 group_message_content 表的 group_msg_id 字段的索引之前,由于 Join 是 ref 类型的,所以我们的执行计划中并没有看到有使用 Join Buffer。

join 优化:

用小结果集驱动大结果集,尽量减少join语句中的Nested Loop的循环总次数;

优先优化Nested Loop的内层循环,因为内层循环是循环中执行次数最多的,每次循环提升很小的性能都能在整个循环中提升很大的性能;

对被驱动表的join字段上建立索引;

当被驱动表的join字段上无法建立索引的时候,设置足够的Join Buffer Size

参考:

http://www.jasongj.com/2015/03/07/Join1/ #强烈推荐读,本文没写全里面的 Hash Join、Merge Join 等分析

http://www.cnblogs.com/weizhenlu/p/5970392.html

http://blog.csdn.net/ghsau/article/details/43762027

http://database.51cto.com/art/200904/117947.htm

http://blog.csdn.net/ys_565137671/article/details/6361730

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/367175.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

手把手教你写一份优质的前端技术简历

不知不觉一年一度的秋招又来了,你收获了哪些大厂的面试邀约,又拿了多少offer呢?你身边是不是有挺多人技术比你差,但是却拿到了很多大厂的offer呢?其实,要想面试拿offer,首先要过得了简历那一关。…

地兵布阵 -----------HDU-1166

C国的死对头A国这段时间正在进行军事演习,所以C国间谍头子Derek和他手下Tidy又开始忙乎了。A国在海岸线沿直线布置了N个工兵营地,Derek和Tidy的任务就是要监视这些工兵营地的活动情况。由于采取了某种先进的监测手段,所以每个工兵营地的人数C国都掌握的一…

python笔记30-docstring注释添加变量

前言 python里面添加字符串注释非常简单,如何将变量放入 python 的函数注释里面呢? docstring也就是给代码加注释的内容了,python可以给函数,类、方法,模块添加注释内容,注释标准格式一般是三个双引号&…

无线路由器在手机上如何连接服务器,192.168.10.1路由器手机怎么设置? | 192路由网...

问:192.168.10.1路由器手机怎么设置?答:192.168.10.1是一个C类的私有IP地址,目前国产的路由器中,睿因路由器使用192.168.10.1作为默认登录地址。鉴于此,下面鸿哥使用睿因路由器来进行演示介绍。温馨提示&am…

Java Mission Control 5.2终于来了! 欢迎7u40!

自从我们上次听说这个叫做任务控制的小东西已经有一段时间了。 它从JRockit一直到现在都被重命名为Java Mission Control。 这是从HotSpot和JRockit融合战略中幸存下来的部分之一。 使用今天的Java SE 7 Update 40,您实际上可以再次使用它。 Java Mission Control …

webview布局适配实践

一、相关概念 1、viewport:移动设备(包括webview)用来显示网页的那一块区域; 2、devicePixelRatio属性(别名像素比,简称dpr):window.devicePixelRatio 物理像素 / 独立像素(css中的px); 3、rem…

mysql count 不等于_Mysql 不同的 count 区别

不同 count 的区别:count(*)、count(主键 id)和 count(1) 都表示返回满足条件的结果集的总行数;而 count(字 段), 则表示返回满足条件的数据行里面,参数“字段”不为 NULL 的总个数。性能:count(主键 id):I…

Python3爬虫(四)请求库的使用requests

Infi-chu: http://www.cnblogs.com/Infi-chu/ 一、基本用法: 1. 安装: pip install requests 2. 例子: import requests url http://www.baidu.com r requests.get(url) print(type(r)) # 类型是str(JSON格式) pr…

后台通过request.setAttribute向前台传值,前台如何去获取其中的对象或属性值

讲这些,我们先来了解一下request.setAttribute和request.setAttribute()这两种方法的作用。 request.getAttribute("nameOfObj"); 可得到jsp页面表单中输入框内的value。(其实表单控件中的Object的name与value是存放在一个哈希表中的&#xff…

两个用于Eclipse的TCK –开源到底有什么?

早在5月,Oracle就向Eclipse Foundation 授予了兼容性测试奖学金 。 在过去的几天里,这引起了媒体的关注,我只是想确保我对整个过程和详细动作有所了解。 看起来像是一见钟情的简单诚实的礼物实际上具有更多的方面。 但让我们从头开始&#xf…

尝试连接到服务器时出错请检查虚拟机管理器,Hyper-V尝试连接到服务器出错无效类的解决方法...

Hyper-V尝试连接到服务器出错无效类的解决方法Windows10安装Hyper-V后没有自动连接到本地计算机,手工连接失败,提示:引用内容尝试连接到服务器"DESKTOP-6P9L2HB"时出错。请检查虚拟机管理服务是否正在运行以及是否授权你连接到此服…

Flask和mysql多线程_Flask解析(二):Flask-Sqlalchemy与多线程、多进程

Sqlalchemyflask-sqlalchemy的session是线程安全的,但在多进程环境下,要确保派生子进程时,父进程不存在任何的数据库连接,可以通过调用db.get_engine(appapp).dispose()来手动销毁已经创建的engine,然后再派生子进程。…

深入理解redis数据类型

转载请注明出处:https://www.cnblogs.com/wenjunwei/p/9720033.html redis的存储模型 redis不是普通的键值对存储,它实际上是一个数据结构存储服务器,可以支持不同类型的值。这意味着redis相比传统键值对字符串key和字符串value存储来说&…

centos7 登陆报错 grep:write error

出现这个原因是因为磁盘空间满了 通过df -h查看存储空间 发现磁盘空间满了,可以用 find / -type f -size 1000M 查找大于1000M的文件删除 然后找到用rm -rf 命令删除 然后就不会出现这个问题了!转载于:https://www.cnblogs.com/lxs1314/p/8961113.html

你敢在post和get上刁难我,就别怪我装逼了

> 掘金编辑提醒:本文疑似有误,参考 听说「99% 的人都理解错了 HTTP 中 GET 与 POST 的区别」 之前好几次面试都被问到post和get有什么区别,肯定很多同学和我一样说了一大堆什么post比get安全,get比post传的少乱起八糟这样的答案…

昂首阔步:让开发人员喜欢使用您的REST API

随着JAX-RS API的发展,以及今年早些时候在JSR-339下发布的2.0版本,使用出色的Java平台创建REST服务变得更加容易。 但是,极大的简化带来了巨大的责任:记录所有这些API,以便其他开发人员可以快速了解如何使用它们。 不…

thinkphp mysql 更新_THINKPHP5修改数据库数据出现“缺少更新条件”的错误

查询数据库的数据分配显示在页面山修改后的数据准备传递到第三章图里接收数据,然后修改到数据问题出现的环境背景及自己尝试过哪些方法相关代码// 请把代码文本粘贴到下方(请勿用图片代替代码)第一张图代码public function edit(){$db_01new DB();$id_editRequest::…

angular ajax get post 参数,Angular的Post 传递参数问题及解决方法

一、传递参数过程中POST会出问题,问题来源:我们都知道向后台传参可以使用get、post,其形式类似于nameiyy&id001 。但是在angular中却发现使用$http post 进行异步传输的过程中后台是接收不到数据的,其实这个问题就是因为请求头…

[No0000187]可能是把Java内存区域讲的最清楚的一篇文章

写在前面(常见面试题) 基本问题: 介绍下 Java 内存区域(运行时数据区)Java 对象的创建过程(五步,建议能默写出来并且要知道每一步虚拟机做了什么)对象的访问定位的两种方式&#xff…

Java语言基础及java核心

一、Java语言特点 1、 简单 2、 面向对象 3、 分布式 4、 健壮 5、 安全 6、 中性架构跨平台 7、 超强的可移植性 8、 高性能 9、 多线程 二、java的环境变量 JAVA_HOMEC:\Program Files\Java\jdk1.8.0_101 (到你的安装目录下) CLASSPASH./ &#xff0…