mysql join越多性能_mysqljoin的原理和优化
前言
前面我们已经了解了MySQLQueryOptimizer的工作原理,学习了Query优化的基本原则和思路,理解了索引选择的技巧,这一节我们将围绕Query语句中使用非常频繁,且随时可能存在性能隐患的Join语句,继续我们的Query优化之旅。
Join 的实现原理
在寻找Join语句的优化思路之前,我们首先要理解在MySQL中是如何来实现Join的,只要理解了实现原理之后,优化就比较简单了。下面我们先分析一下MySQL中Join的实现原理。
在MySQL中,只有一种Join算法,就是大名鼎鼎的NestedLoopJoin,他没有其他很多数据库所提供的HashJoin,也没有SortMergeJoin。顾名思义,NestedLoopJoin实际上就是通过驱动表的结果集作为循环基础数据,然后一条一条的通过该结果集中的数据作为过滤条件到下一个表中查询数据,然后合并结果。如果还有第三个参与Join,则再通过前两个表的Join结果集作为循环基础数据,再一次通过循环查询条件到第三个表中查询数据,如此往复。
下面我们将通过一个三表Join语句示例来说明MySQL的NestedLoopJoin实现方式。
注意:由于要展示Explain中的一个在MySQL5.1.18才开始出现的输出信息(在之前版本中只是没有输出信息,实际执行过程并没有变化),所以下面的示例环境是MySQL5.1.26。
Query 如下:
select m.subject msg_subject, c.content msg_content
from user_group g,group_message m,group_message_content c
where g.user_id = 1
and m.group_id = g.group_id
and c.group_msg_id = m.id
为了便于示例,我们通过如下操作为group_message表增加了一个group_id的索引:
create index idx_group_message_gid_uid on group_message(group_id);
然后看看我们的Query的执行计划:
sky@localhost : example 11:17:04> explain select m.subject msg_subject, c.content
msg_content -> from user_group g,group_message m,group_message_content c -> where g.user_id = 1 -> and m.group_id = g.group_id -> and c.group_msg_id = m.id\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: g
type: ref
possible_keys: user_group_gid_ind,user_group_uid_ind,user_group_gid_uid_ind
key: user_group_uid_ind
key_len: 4
ref: const
rows: 2
Extra:
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: m
type: ref
possible_keys: PRIMARY,idx_group_message_gid_uid
key: idx_group_message_gid_uid
key_len: 4
ref: example.g.group_id
rows: 3
Extra:
*************************** 3. row ***************************
id: 1
select_type: SIMPLE
table: c
type: ref
possible_keys: idx_group_message_content_msg_id
key: idx_group_message_content_msg_id
key_len: 4
ref: example.m.id
rows: 2
Extra:
我们可以看出,MySQLQueryOptimizer选择了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比较进行循环查询,才得到最终的结果。
这个过程可以通过如下表达式来表示:
for each record g_rec in table user_group that g_rec.user_id=1{
for each record m_rec in group_message that m_rec.group_id=g_rec.group_id{
for each record c_rec in group_message_content that c_rec.group_msg_id=m_rec.id
pass the (g_rec.user_id, m_rec.subject, c_rec.content) row
combination to output;
} }
下图可以更清晰的标识出实际的执行情况:
假设我们去掉group_message_content表上面的group_msg_id字段的索引,然后再看看执行计划会变成怎样:
sky@localhost : example 11:25:36> drop index idx_group_message_content_msg_id on
group_message_content;
Query OK, 96 rows affected (0.11 sec)
sky@localhost : example 10:21:06> explain
-> select m.subject msg_subject, c.content msg_content
-> from user_group g,group_message m,group_message_content c
-> where g.user_id = 1
-> and m.group_id = g.group_id
-> and c.group_msg_id = m.id\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: g
type: ref
possible_keys: idx_user_group_uid
key: idx_user_group_uid
key_len: 4
ref: const
rows: 2 Extra:
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: m
type: ref
possible_keys: PRIMARY,idx_group_message_gid_uid
key: idx_group_message_gid_uid
key_len: 4
ref: example.g.group_id
rows: 3
Extra: *************************** 3. row ***************************
id: 1
select_type: SIMPLE
table: c
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 96
Extra: Using where; Using join buffer
我们看到不仅仅user_group表的访问从ref变成了ALL,此外,在最后一行的Extra信息从没有任何内容变成为Usingwhere;Usingjoinbuffer,也就是说,对于从ref变成ALL很容易理解,没有可以使用的索引的索引了嘛,当然得进行全表扫描了,Usingwhere也是因为变成全表扫描之后,我们需要取得的content字段只能通过对表中的数据进行where过滤才能取得,但是后面出现的Usingjoinbuffer是一个啥呢?
实际上,这里的Join正是利用到了我们在之前“MySQLServer性能优化”一章中所提到的一个Cache参数相关的内容,也就是我们通过join_buffer_size参数所设置的JoinBuffer。
实际上,JoinBuffer只有当我们的Join类型为ALL(如示例中),index,rang或者是index_merge的时候才能够使用,所以,在我们去掉group_message_content表的group_msg_id字段的索引之前,由于Join是ref类型的,所以我们的执行计划中并没有看到有使用JoinBuffer。
当我们使用了JoinBuffer之后,我们可以通过下面的这个表达式描述出示例中我们的Join完成过程:
for each record g_rec in table user_group{
for each record m_rec in group_message that m_rec.group_id=g_rec.group_id
{
put (g_rec, m_rec) into the buffer if (buffer is full) flush_buffer();
}
}
flush_buffer(){
for each record c_rec in group_message_content that c_rec.group_msg_id = c_rec.id{
for each record in the buffer
pass (g_rec.user_id, m_rec.subject, c_rec.content) row combination to output; }
empty the buffer;
}
当然,如果通过类似于上面的图片来展现或许大家会觉得更容易理解一些,如下:
通过上面的示例,我想大家应该对MySQL中NestedJoin的实现原理有了一个了解了,也应该清楚MySQL使用JoinBuffer的方法了。当然,这里并没有涉及到外连接的内容,实际对于外连接来说,可能存在的区别主要是连接顺序以及组合空值记录方面。
Join 语句的优化
在明白了MySQL中Join的实现原理之后,我们就比较清楚的知道该如何去优化一个一个Join语句了。
1.尽可能减少Join语句中的NestedLoop的循环总次数;如何减少NestedLoop的循环总次数?最有效的办法只有一个,那就是让驱动表的结果集尽可能的小,这也正是在本章第二节中的优化基本原则之一“永远用小结果集驱动大的结果集”。
为什么?因为驱动结果集越大,意味着需要循环的次数越多,也就是说在被驱动结果集上面所需要执行的查询检索次数会越多。比如,当两个表(表A和表B)Join的时候,如果表A通过WHERE条件过滤后有10条记录,而表B有20条记录。如果我们选择表A作为驱动表,也就是被驱动表的结果集为20,那么我们通过Join条件对被驱动表(表B)的比较过滤就会有10次。反之,如果我们选择表B作为驱动表,则需要有20次对表A的比较过滤。
当然,此优化的前提条件是通过Join条件对各个表的每次访问的资源消耗差别不是太大。如果访问存在较大的差别的时候(一般都是因为索引的区别),我们就不能简单的通过结果集的大小来判断需要Join语句的驱动顺序,而是要通过比较循环次数和每次循环所需要的消耗的乘积的大小来得到如何驱动更优化。
2.优先优化NestedLoop的内层循环;
不仅仅是在数据库的Join中应该做的,实际上在我们优化程序语言的时候也有类似的优化原则。内层循环是循环中执行次数最多的,每次循环节约很小的资源,在整个循环中就能节约很大的资源。
3.保证Join语句中被驱动表上Join条件字段已经被索引;
保证被驱动表上Join条件字段已经被索引的目的,正是针对上面两点的考虑,只有让被驱动表的Join条件字段被索引了,才能保证循环中每次查询都能够消耗较少的资源,这也正是优化内层循环的实际优化方法。
4.当无法保证被驱动表的Join条件字段被索引且内存资源充足的前提下,不要太吝惜JoinBuffer的设置;
当在某些特殊的环境中,我们的Join必须是All,Index,range或者是index_merge类型的时候,JoinBuffer就会派上用场了。在这种情况下,JoinBuffer的大小将对整个Join语句的消耗起到非常关键的作用
mysql join越多性能_mysqljoin的原理和优化相关推荐
- 浅析Mysql Join语法以及性能优化
在讲MySQL的Join语法前还是先回顾一下联结的语法,呵呵,其实连我自己都忘得差不多了,那就大家一起温习吧,这里我有个比较简便的记忆方法,内外联结的区别是内联结将去除所有不符合条件的记录,而外联结则 ...
- mysql join 与 cross join 效率_浅析Mysql Join语法以及性能优化
在讲MySQL的Join语法前还是先回顾一下联结的语法,呵呵,其实连我自己都忘得差不多了,那就大家一起温习吧,这里我有个比较简便的记忆方法,内外联结的区别是内联结将去除所有不符合条件的记录,而外联结则 ...
- MySQL中order by语句的实现原理以及优化手段
相信很多人在面试过程中,总被问到有没有 SQL 调优经验,对于工作经验年限较少的求职者,通常都是在面试之前从网上百度一些答案,提前背熟,然后面试的时候直接将提前背好的答案说出来.笔者作为一名菜鸟,在刚 ...
- 【重要】MySQL中order by语句的实现原理以及优化手段
写在前面 filesort排序算法参考 :https://blog.csdn.net/lijingkuan/article/details/70341176 双路排序:是首先根据相应的条件取出相应的排 ...
- mysql+join的原理,Mysql连接join查询原理知识点
Mysql连接join查询原理知识点 Mysql连接(join)查询 1.基本概念 将两个表的每一行,以"两两横向对接"的方式,所得到的所有行的结果. 假设: 表A有n1行,m1列 ...
- MySQL JOIN原理
为什么80%的码农都做不了架构师?>>> 最近被公司某一开发问道JOIN了MySQL JOIN的问题,细数之下发下我对MySQL JOIN的理解并不是很深刻,所以也查看了很多文档,最 ...
- mysql explain详解_数据库mysql(1)——B+TREE索引原理
一.B+Tree索引详解 1.什么是索引? 索引:加速查询的数据结构. 2.索引常见数据结构: #1.顺序查找: 最基本的查询算法-复杂度O(n),大数据量此算法效率糟糕. #2.二叉树查找(bina ...
- mysql limit分页知乎_头发一天天的掉,你知道MySQL的Limit有性能问题吗?
你知道MySQL的Limit有性能问题吗? MySQL的分页查询通常通过limit来实现. MySQL的limit基本用法很简单.limit接收1或2个整数型参数,如果是2个参数,第一个是指定第一个返 ...
- 从MySQL得到最大的性能
10 从MySQL得到最大的性能 优化是一项复杂的任务,因为它最终需要对整个系统的理解.当用你的系统/应用的小知识做一些局部优化是可能的时候,你越想让你的系统更优化,你必须知道它也越多. 因此,本章将 ...
最新文章
- 百度二面:一个线程OOM了,其它线程还能运行吗?
- 快速小目标检测--Feature-Fused SSD: Fast Detection for Small Objects
- spring boot 项目源码_Spring Boot2 系列教程(三)理解 Spring Boot 项目中的 parent
- MySQL字符集编码
- 笔记-项目整体管理-项目管理计划
- Linux redis配置服务,Linux 安装 redis 并配置成 service 系统服务
- 计算机中职生毕业鉴定评语,中职学生毕业鉴定评语
- linux 屏幕输出 高亮_通过printf设置Linux终端输出的颜色和显示方式
- 989. 数组形式的整数加法
- requests库学习
- 扫雷游戏网页版_如何成为扫雷高手,数学老师教你玩转这款复古小游戏
- C语言IDE和编辑器比较
- FreeBSD搭建Nginx+Apache24+php56+mysql56手把手一步步的笔记
- vs下qt混合编译错误:MOC问题
- 标准C++之运算符重载和虚表指针
- echarts图片的打印问题
- HTML大文件上传解决方案实例代码
- 高薪邀请国人去菲律宾上班的新型诈骗方式
- Arduino米思齐Mixly---声音+人体红外检测+亮度检测,模拟楼道灯
- java url hostname_如何从URL字符串中提取主机名和端口?(How to extract hostname and port from URL string?)...
热门文章
- SQL开发技巧(二) 【转】感觉他写的很好
- 生成 linq to sharepoint 对象模型sharepoint 母板页
- 血泪八条---给中国IT教育培训机构
- SCOPE_IDENTITY()、 @@IDENTITY 、 IDENT_CURRENT()
- JavaScript对象学习笔记
- 6-6-阶段案例:传智书城JSP页面
- vscode运行html的插件_vscode前端常用插件推荐,搭建JQuery、Vue等开发环境
- 代码阅读器 android,适用于Android的条形码/ Qr代码阅读器
- 设计灵感|高饱和渐变创意海报设计作品,值得学习
- 清新淡雅水彩手绘花卉插画素材,psd分层好用!