大家好, 今天我们来聊一聊Mysql中的join原理。
join用法基本工作过的都会用,不管是left join、right join、inner join语法都是比较简单的。
但是,join的原理确实博大精深,对于一些传统it企业,几乎是一句sql走天下,join了五六个表,当数据量上来的时候,就会变得非常慢,索引对于掌握join的优化还是非常有必要的。
阿里的开发手册中规定join不能查过三个,有些互联网是明确规定不能使用join的的明文规定,那么在实际的场景中,我们真的不能使用join吗?我们就来详细的聊一聊。
Mysql的join主要涉及到三种算法,分别是Simple Nested-Loop Join、Block Nested-Loop Join、Index Nested-Loop Join,下面我们就来深入的了解这三种算法的原理、区别、效率。
首先,为了测试先准备两个表作为测试表,并且使用存储过程初始化一些测试数据,初始化的表结构sql如下所示:
CREATE TABLE `testa` (
`id` int(20) NOT NULL AUTO_INCREMENT COMMENT '活动主键',
`col1` int(20) NOT NULL DEFAULT '0' COMMENT '测试字段1',
`col2` int(20) NOT NULL DEFAULT '0' COMMENT '测试字段2',
PRIMARY KEY (`id`),
KEY `col1` (`idx_col1`)
)ENGINE=InnoDB AUTO_INCREMENT=782 DEFAULT CHARSET=utf8mb4 COMMENT='测试表1';
CREATE TABLE `testb` (
`id` int(20) NOT NULL AUTO_INCREMENT COMMENT '活动主键',
`col1` int(20) NOT NULL DEFAULT '0' COMMENT '测试字段1',
`col2` int(20) NOT NULL DEFAULT '0' COMMENT '测试字段2',
PRIMARY KEY (`id`),
KEY `col1` (`idx_col1`)
) ENGINE=InnoDB AUTO_INCREMENT=782 DEFAULT CHARSET=utf8mb4 COMMENT='测试表2';
初始化数据:
CREATE DEFINER = `root` @`localhost` PROCEDURE `init_data` ()
BEGIN
DECLARE i INT;
SET i = 1;
WHILE ( i <= 100 ) DO
INSERT INTO testa VALUES ( i, i, i );
SET i = i + 1;
END WHILE;
SET i = 1;
WHILE ( i <= 2000) DO
INSERT INTO test2 VALUES ( i, i, i );
SET i = i + 1;
END WHILE;
END
分别初始化testa表为100条数据,testb为2000条数据
首先,我们执行如下sql:
select * from testa ta left join testb tb on (ta.col1=tb.col2);
Simple Nested-Loop Join是最简单也是最粗暴的join方法,上面的sql在testb 的col2字段是没有加索引的,所以当testa为驱动表,testb为被驱动表时,就会拿着testa的每一行,然后去testb的全表扫描,执行流程如下:
因此扫描的时间复杂度就是100*2000=20W的行数,所以在被驱动表关联字段没有添加索引的时候效率就非常的低下。
假如testb是百万数据以上,那么扫描的时间复杂度就更恐怖了,但是在Mysql中没有使用这个算法,而是使用了另一种算法Block Nested-Loop Join,目的就是为了优化驱动表没有索引时的查询。
还是上面的sql,不过通过加explain关键字来查看这条sql的执行计划:
explain select * from testa ta left join testb tb on (ta.col1=tb.col2);
可以看到testb依旧是全表扫描,并且在Extra字段中可以看到testb的Using join buffer(hash join)的字样,在rows中可以看到总扫描的行数是驱动表行数+被驱动表行数,那么这个算法与Simple Nested-Loop Join有什么区别呢?
Block Nested-Loop Join算法中引入了join buffer区域,而join buffer是一块内存区域,它的大小由join_buffer_size参数大小控制,默认大小是256k:
在执行上面的sql的时候,它会把testa表的数据全部加载到join buffer区域,因为join buffer是内存操作,因此相对于比上面的simple算法要高效,具体的执行流程如下:
具体的流程图如下所示:
所以,从上面的执行的步骤来看(假设驱动表的行数为N,被驱动表的行数据为M),Block Nested-Loop Join的扫描的行数还是驱动表+被驱动表行数(N+M),在内存中总的比较次数还是驱动表*被驱动表行数(N*M)
上面我们提到join buffer是一块内存区域,并且有自己的大小,要是join buffer的大小不足够容纳驱动表的数量级怎么办呢?
答案就是分段,你要是join buffer没办法容纳驱动表的所有数据,那么就不把所有的数据加载到join buffer里面,先加载一部分,后面再加载另一部分,比如:先加载testa中的80条数据,与testb比较完数据后,清空再加载testa后20条数据,再与testb进行比较。具体执行流程如下:
执行流程图如下所示:
从上面的结果来看相对于比内存足够的join buffer来说,分段的join buffer多了一遍全表全表遍历testb,并且分的段数越多,多扫描驱动表的次数就越多。,性能就越差,所以在某一些场景下,适当的增大join buffer的值,是能够提高join的效率。
假如驱动表的行数是N,分段参数为K,被驱动表的行数是M,那么总的扫描行数还是N+K*M,而内存比较的次数还是N*M,没有变。
其中K段数与N的数据量有关,若是N的数据量越大,那么可能K被分成段数就越多,这样多次重复扫描的被驱动表的次数就越多。
所以在join buffer不够的情况小,驱动表是越小越好,能够减少K值,减少重复扫描被驱动表的次数。这也就是为什么提倡小表要作为驱动表的原因。
那么这里提到小表的概念,是不是就是数据量少的就是认为是小表呢?其实不然,小表的真正的还是是实际参与join的数据量,比如以下的两条sql:
select * from testa ta left join testb tb on (ta.col1=tb.col2) where tb.id<=20;
select * from testb tb left join testa ta on (ta.col1=tb.col2) where tb.id<=20;
在第二条sql中,虽然testb驱动表数据量比较大,但是在where条件中实际参与join的行数也就是id小于等于20的数据,完全小于testa的数据量,所以这里选择以testb作为驱动表是更加的合适。
在实际的开发中Block Nested-Loop Join也是严禁被禁止出现的,严格要求关联条件建索引,所以性能最好的就是Index Nested-Loop Join算法。
当我们执行如下sql时:
select * from testa ta left join testb tb on (ta.col1=tb.col1);
它的执行流程如下:
因为testb的col1字段是建立了索引,所以,当使用testa表的字段col1去testb查找的时候,testb走的是col1索引的b+树的搜索,时间复杂度近似log2M,并且因为是select*,也就是要查找testb的所有字段,所以这里也涉及到回表查询,因此就变成了2*log2M,若是不懂回表的,可以参考这一篇文章:[十万个为什么,精通Mysql索引]
在这个过程中,testa表的扫描行数是全部,所以需要扫描100行,然后testa的每一行都与testb也是一一对应的,所以col1索引查询扫描的行数也是100行,所以总的扫描行数就是200行。
我们假设驱动表的数据行位N,被驱动表的数据行为M,那么近似的复杂度为:N+N*2*log M,因为驱动表的扫描行数就是N,然后被驱动表因为每一次都对应驱动表的一次,并且一次的时间复杂度就是近似2*log M,所以被驱动表就是N*2*log M。
明显N的值对于N+N*2*log M的结果值影响更大,所以N越小越好,所以选择小表作为驱动表是最优选择。
在一些情况下的优化,假如join的驱动表所需要的字段很少(两个),可以建立联合索引来优化join查询,并且如果业务允许的话,可以通过冗余字段,减少join的个数提高查询的效率。
好了,这一期就分享join的原理,以及join一些优化的手段和注意的事项,我们下一期见。
《Mysql 45讲》
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/0Li0w_9W7U5jPcAU92XZCg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。