我的编程空间,编程开发者的网络收藏夹
学习永远不晚

技术分享 | MySQL 优化:JOIN 优化实践

短信预约 信息系统项目管理师 报名、考试、查分时间动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

技术分享 | MySQL 优化:JOIN 优化实践

技术分享 | MySQL 优化:JOIN 优化实践

近期刚好学习了丁奇老师的《MySQL 实战 45 讲》中的 join 优化相关知识,又刚刚好碰上了一个非常切合的 join 查询需要优化,分析过程有些曲折,记录下来留作笔记。

问题 SQL 描述

问题 SQL 和执行计划是这样的:

explain SELECT
    t1.stru_id AS struId,
    ...
FROM cams_stru_info t1
    LEFT JOIN cams_mainframerel t2 ON t1.stru_id =t2.stru_id
WHERE t1.stru_state="1";

这个 SQL 是非常简单的,关联条件 stru_id 在两张表中都是主键或者主键的第一个字段:

而把 left join 转化成 inner join 后,SQL的效率很高:

从上述信息来看,这个 SQL 存在的问题有:

大表驱动小表,这肯定是不好的,t1表近11万行数据,为驱动表;t2表近1.9万行数据,为被驱动表。这主要是 left join 导致的,大部分情况下 left join 左表即驱动表,但是这里业务需求就是如此,没办法改变; 2.驱动表的筛选条件 stru_state = 1,这个字段是一个状态值,基数很小,不适合建索引,即使建索引也没有用,所以驱动表一定是全表扫描。这点根据业务需求,也没法改变,其实全表扫描对性能影响不大,后续会解释; 3.被驱动表关联字段明明有索引,但做了全表扫描(全索引扫描); 4.优化器选择使用的 join 算法为 BNL(Block Nested Loop),SQL 执行是计算次数等于 11 万 * 1.9 万,近 20 亿次计算,所以执行非常慢。

join 的两种算法:BNL 和 NLJ

在继续分析之前,先得介绍一下 join 的两种算法,方便大家理解后面我分析思路上的错误和心得。 首先是 NLJ(Index Nested-Loop Join)算法,以如下 SQL 为例:

select * from t1 join t2 on t1.a=t2.a

SQL 执行时内部流程是这样的:

先从 t1(假设这里 t1 被选为驱动表)中取出一行数据 X; 2.从 X 中取出关联字段 a 值,去 t2 中进行查找,满足条件的行取出; 3.重复1、2步骤,直到表 t1 最后一行循环结束。

这就是一个嵌套循环的过程,注意“Index”,所以这里前提是被驱动表的关联字段有索引,最明显的特征就是在被驱动表上查找数据时可以使用索引,总的对比计算次数等于驱动表满足 where 条件的行数。假设这里 t1、t2都是1万行,则只需要 1万次计算。

如果 t1、t2 的 a 字段都没有索引,还按照上述的嵌套循环流程查找数据呢?每次在被驱动表上查找数据时都是一次全表扫描,要做1万次全表扫描,扫描行数等于 1万+1万*1万,这个效率很低,如果表行数更多,扫描行数动辄几百亿,所以优化器肯定不会使用这样的算法,而是选择 BNL 算法,执行流程是这样的:

把 t1 表(假设这里 t1 被选为驱动表)满足条件的数据全部取出放到线程的 join_buffer 中; 每次取 t2 表一行数据,去 join_buffer 中进行查找,满足条件的行取出,直到表 t2 最后一行循环结束。 这个算法下,执行计划的 Extra 中会出现 Using join buffer(Block Nested Loop),t1、t2 都做了一次全表扫描,总的扫描行数等于 1万+1万。但是由于 join_buffer 维护的是一个无序数组,每次在 join_buffer 中查找都要遍历所有行,总的内存计算次数等于1万*1万。说句题外话,如果 join_buffer 维护的是一个哈希表的话,每次查找做一次判断就能找到数据,效率提升飞快,其实这就是 hash join 了,MySQL 8.0 已支持。另外如果 join_buffer 不够大放不下驱动表的数据,则要分多次执行上面的流程,会导致被驱动表也做多次全表扫描。

分析误区

回到分析过程,我一开始疑惑的点就在于:为什么被驱动表 t2 关联字段有索引,却没有使用 NLJ 算法,而是使用了 BNL 算法?显然如果使用 NLJ 算法,总的扫描行数等于 t1 的行数即 19万行,总的计算次数也只有19万次,效率是很高的。

因为是刚学到 join 算法这方面的知识,理解的不是很透彻,思路上一直纠结在算法这里,所以接下来我想的是禁用 BNL 算法,搜索了一下 hint 语法:"select /*+ NO_BNL() / t1. from ...",执行计划的结果却跟我预期的不一样:

这让我更迷惑了,明明没有使用 BNL 算法,为什么被驱动表还是做了全表扫描?是算法出了什么问题吗?还是 hint 产生了其他效果?

直到客户告诉了我答案,两表的关联字段字符集和校对规则不一样...

得解释下为什么之前没有想这一点,因为前面提到 inner join 执行计划毫无问题,使用了 NLJ 算法,优化器选了小表 t2 做驱动表,被驱动表 t1 按索引查找,效率很高。

继续分析

得知原因后,关于算法的疑问突然就想通了,NLJ 和 BNL 算法的选择根本在于关联字段的索引:*不是取决于有没有索引,而是被驱动表能不能使用到索引进行查找。*所以这本质上是一个索引失效问题,逻辑上其实只推进了一步,但是因为对新知识的不自信,推理能力不足(之前自认为推理能力不错的...),这一步一直没有走出去,这应该是我最大的收获了。

然后还要解释另一个疑问:既然关联字段字符集和校对规则不一样,为什么 inner join 不受影响?left join 时却索引失效了?

来看个测试,下面是两张表,关联字段的字符集不一样:

CREATE TABLE `t3` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `a` char(50) CHARACTER SET utf8 DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_a` (`a`)) ;
CREATE TABLE `t4` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `b` char(50) CHARACTER SET latin1 DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_b` (`b`));

分别插入了几条数据,使用 straight_join 语法固定连接顺序:

SQL1:select * from t3 straight_join t4 on t3.a=t4.b;
SQL2:select * from t4 straight_join t3 on t3.a=t4.b;
SQL3:select * from t3 left join t4 on t3.a=t4.b;
SQL4:select * from t3 join t4 on t3.a=t4.b;

SQL1 和 SQL3 都是选择了 t3 做驱动表,执行计划一样,都显示索引失效了,使用了 BNL 算法,被驱动表进行全表扫描:

SQL2 和 SQL4 都是选择了 t4 做驱动表,执行计划一样,被驱动表按照索引查找,使用了 NLJ 算法:

也就是说,在这个测试中,latin1 去 join utf8 时,索引是正常使用的,反过来则索引失效。又测试了 utf8 和 utf8mb4 的情况,utf8 join utf8mb4 正常,反过来则索引失效。为此我的猜测是:被驱动表字段的字符集更大时,索引可以正常使用,反之则索引失效。关于字符集这点就不继续探索了,希望能有这方面的高手来解答。

最后,SQL 改成 inner join 后使用 NLJ 算法的原因就很明了了:NLJ 算法的效率显然是高于 BNL 的,优化器做选择时当然要选择更高效的算法。虽然关联字段字符集不一样,但是按照小>大的顺序,索引还是可以正常使用,一旦索引可以使用,选择 NLJ 算法就是顺理成章的事了。

总结

NLJ 和 BNL 算法的选择根本在于关联字段的索引:不是取决于有没有索引,而是被驱动表能不能使用到索引进行查找; 2.join 查询关联字段字符集或者校对规则不一致导致的索引失效,跟关联顺序有关,当然规范一定是让各表关联字段的字符集和校对规则一致; 3.join 的优化,最好的办法就是把 BNL 转化为 NLJ,也就是被驱动表关联字段加索引,并且保证其有效,更多的优化思路可以看参考资料。 另外,一个好消息是从 MySQL8.0.18 开始已经支持 hash join 了,原本选择 BNL 算法的场景会直接使用 hash join,效率提升不止一点点,简直就是 DBA 福音了。

参考资料 https://time.geekbang.org/column/article/79700 https://time.geekbang.org/column/article/80147 https://time.geekbang.org/column/article/82865

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

技术分享 | MySQL 优化:JOIN 优化实践

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

技术分享 | MySQL 优化:JOIN 优化实践

近期刚好学习了丁奇老师的《MySQL 实战 45 讲》中的 join 优化相关知识,又刚刚好碰上了一个非常切合的 join 查询需要优化,分析过程有些曲折,记录下来留作笔记。问题 SQL 描述问题 SQL 和执行计划是这样的:explain SELECT
技术分享 | MySQL 优化:JOIN 优化实践
2015-01-09

技术分享 | MySQL 子查询优化

作者:胡呈清爱可生 DBA 团队成员,擅长故障分析、性能优化,个人博客:https://www.jianshu.com/u/a95ec11f67a8,欢迎讨论。本文来源:原创投稿*爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。有这
技术分享 | MySQL 子查询优化
2016-07-07

技术分享 | 用好 MySQL 的 MRR 优化器

作者:蒋乐兴MySQL DBA,擅长 python 和 SQL,目前维护着 github 的两个开源项目:mysqltools 、dbmc 以及独立博客:https://www.sqlpy.com。本文来源:原创投稿*爱可生开源社区出品,原创内容未经授权不得随
技术分享 | 用好 MySQL 的 MRR 优化器
2014-12-31

MySQL性能优化技巧分享

MySQL性能优化在互联网公司MySQL的使用非常广泛,大家经常会有MySQL性能优化方面的需求。整理了一些在MySQL优化方面的实用技巧。Schema与数据类型优化整数通常是标识列最好的选择,因为它们很快并且可以使用AUTO_INCREM
2022-05-20

mysql join优化的技巧有哪些

优化MySQL JOIN操作可以提高查询性能,以下是一些常见的优化技巧:1. 使用合适的索引:确保参与JOIN的列都有合适的索引,这样可以加快连接操作的速度。可以使用EXPLAIN语句来分析查询计划,查看是否使用了索引。2. 尽量避免在JO
2023-10-23

PHP 性能优化:微调技术与实践

为了卓越的 php 性能,文章提供了以下微调技术:缓存优化:使用文件缓存或 memcached 缓存频繁访问的数据。对象缓存:使用 apc 或 xcache 存储经常使用的 php 对象。代码优化:优化算法和数据结构,例如哈希表或红黑树。数
PHP 性能优化:微调技术与实践
2024-05-11

MySQL索引优化分享

2,explain的作⽤ 查看表的读取顺序,读取操作类型,有哪些索引可用,表之间关联,每张表中有哪些索引被优化器执⾏3,索引命中策略略分析    最左匹配原则 在索引字段上加入函数(不匹配索引)    is null/is not null/not in(不匹
MySQL索引优化分享
2016-09-28

Golang编译优化技巧分享

Golang编译优化技巧分享 为了提高Golang程序的性能和效率,优化编译过程是至关重要的。本文将分享一些Golang编译的优化技巧,并提供具体的代码示例供读者参考。一、使用编译器优化标志Golang编译器提供了一系列优化标志,通过
Golang编译优化技巧分享
2024-03-07

PHP技术分享:优化QQ空间页面设计

在互联网时代,网页设计变得越来越重要,一个好的页面设计可以吸引用户,提高用户体验,增加页面访问量。在开发网页的过程中,如何优化页面设计,提高页面加载速度成为了开发者们关注的焦点。本文将以优化QQ空间页面设计为例,分享一些PHP技术优化方法,
PHP技术分享:优化QQ空间页面设计
2024-03-15

技术分享 | 半一致性读对 Update 的优化

作者:赵黎明爱可生 MySQL DBA 团队成员,Oracle 10g OCM,MySQL 5.7 OCP,擅长数据库性能问题诊断、事务与锁问题的分析等,负责处理客户 MySQL 及我司自研 DMP 平台日常运维中的问题,对开源数据库相关技术非常感兴趣。本文来
技术分享 | 半一致性读对 Update 的优化
2015-10-13

PHP 函数调用性能优化实践分享

为提升 php 应用性能,优化函数调用至关重要。实践包括:减少不必要的函数调用(如重复调用、传入不必要参数)利用函数别名和缩写使用内联函数(提升简单函数调用的性能)PHP 函数调用性能优化实践分享在 PHP 开发中,函数调用会消耗大量时间
PHP 函数调用性能优化实践分享
2024-04-17

PHP 性能优化:社区分享与最佳实践

通过结合社区经验和最佳实践,可有效优化 php 应用性能。社区分享的技巧包括使用缓存、优化数据库查询和使用 cdn。最佳实践包括启用 opcache、使用队列、避免代码重复和进行性能监控。实践案例表明,通过使用缓存机制缓存常见数据库查询结果
PHP 性能优化:社区分享与最佳实践
2024-05-11

Golang 技术性能优化中如何实现分布式性能优化?

如何实现 golang 分布式性能优化?并发编程: 利用 goroutine 并行执行任务。分布式锁: 使用互斥锁防止并发操作导致数据不一致。分布式缓存: 使用 memcached 减少对慢速存储的访问。消息队列: 使用 kafka 解耦任
Golang 技术性能优化中如何实现分布式性能优化?
2024-05-12

Discuz论坛SEO优化技巧分享

Discuz论坛一直以来是国内最受欢迎的论坛软件之一,它功能强大、灵活性高,而且支持SEO优化,有助于提高论坛的搜索引擎可见性和流量。本文将分享一些Discuz论坛SEO优化的技巧,包括具体的代码示例,帮助网站管理员更好地优化论坛以提升排名
Discuz论坛SEO优化技巧分享
2024-03-12

SEO优化技术的示例分析

这篇文章给大家分享的是有关SEO优化技术的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。如何设置SEO关键词当然重要,但SEO优化技术如果只是机械式的说该做什麽不做什麽,而涉及的步骤又非常死板,例如:将焦
2023-06-10

PHP搜索功能优化技巧分享

PHP搜索功能一直是网站开发中非常重要的一环,因为用户往往通过搜索框来查找所需信息。然而,不少网站在实现搜索功能时存在效率低下、搜索结果不准确等问题。为了帮助大家优化PHP搜索功能,本文将分享一些技巧,并提供具体的代码示例。1. 使用全文
PHP搜索功能优化技巧分享
2024-03-06

编程热搜

目录