left join 索引失效无条件_技术分享 | MySQL 优化:JOIN 优化实践
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 存在的问题有:
1. 大表驱动小表,这肯定是不好的,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 执行时内部流程是这样的:
1. 先从 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 算法,执行流程是这样的:
1. 把 t1 表(假设这里 t1 被选为驱动表)满足条件的数据全部取出放到线程的 join buffer 中;
2. 每次取 t2 表一行数据,去 joinbuffer 中进行查找,满足条件的行取出,直到表 t2 最后一行循环结束。
这个算法下,执行计划的 Extra 中会出现 Using join buffer(Block Nested Loop),t1、t2 都做了一次全表扫描,总的扫描行数等于 1万+1万。但是由于 joinbuffer 维护的是一个无序数组,每次在 joinbuffer 中查找都要遍历所有行,总的内存计算次数等于1万*1万。说句题外话,如果 joinbuffer 维护的是一个哈希表的话,每次查找做一次判断就能找到数据,效率提升飞快,其实这就是 hash join 了,MySQL 8.0 已支持。另外如果 joinbuffer 不够大放不下驱动表的数据,则要分多次执行上面的流程,会导致被驱动表也做多次全表扫描。
分析误区
回到分析过程,我一开始疑惑的点就在于:为什么被驱动表 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 算法就是顺理成章的事了。总结1. NLJ 和 BNL 算法的选择根本在于关联字段的索引:不是取决于有没有索引,而是被驱动表能不能使用到索引进行查找;2. join 查询关联字段字符集或者校对规则不一致导致的索引失效,跟关联顺序有关,当然规范一定是让各表关联字段的字符集和校对规则一致;3. join 的优化,最好的办法就是把 BNL 转化为 NLJ,也就是被驱动表关联字段加索引,并且保证其有效,更多的优化思路可以看参考资料。另外,一个好消息是从 MySQL8.0.18 开始已经支持 hash join 了,原本选择 BNL 算法的场景会直接使用 hash join,效率提升不止一点点,简直就是 DBA 福音了。
参考资料:https://time.geekbang.org/column/article/79700https://time.geekbang.org/column/article/80147https://time.geekbang.org/column/article/82865
社区近期动态
点一下“阅读原文”了解更多资讯
left join 索引失效无条件_技术分享 | MySQL 优化:JOIN 优化实践相关推荐
- left join 索引失效无条件_从零开始学数据分析-mysql索引优化方案
接上节,执行计划还有一个重要的字段是extra,以下是出现的选项. 1.using filesort: 需要一次额外的查询或者排序,性能开销大.常见于order by语句中. create 对于单索引 ...
- mysql 走索引 很慢_技术分享 | MySQL优化:为什么SQL走索引还那么慢?
作者:胡呈清 背景 2019-01-11 9:00-10:00 一个 MySQL 数据库把 CPU 打满了. 硬件配置:256G 内存,48 core 分析过程 接手这个问题时现场已经不在了,信息有限 ...
- mysql优化说出九条_技术分享 | MySQL 优化:为什么 SQL 走索引还那么慢?
原标题:技术分享 | MySQL 优化:为什么 SQL 走索引还那么慢? 背景 2019-01-11 9:00-10:00 一个 MySQL 数据库把 CPU 打满了. 硬件配置:256G 内存,48 ...
- mysql 行锁 超时_技术分享 | MySQL 行锁超时排查方法优化
作者:xuty 本文来源:原创投稿 * 爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源. 一.大纲 #### 20191219 10:10:10,234 | com.ali ...
- bcp 不能调用where 子句_技术分享 || Mysql中IS NULL、IS NOT NULL不能走索引?
mysql中IS NULL.IS NOT NULL不能走索引? 不知道是啥原因也不知道啥时候, 江湖上流传着这么一个说法 mysql查询条件包含IS NULL.IS NOT NULL.!=.like ...
- 多少行数_技术分享 | MySQL:查询字段数量多少对查询效率的影响
作者:高鹏 文章末尾有他著作的<深入理解 MySQL 主从原理 32 讲>,深入透彻理解 MySQL 主从,GTID 相关技术知识. 这个问题是最近一个朋友问我的.刚好就好好看了一下,留下 ...
- date转timestamp格式_技术分享 | MySQL:timestamp 时区转换导致 CPU %sy 高的问题
作者:高鹏文章末尾有他著作的<深入理解 MySQL 主从原理 32 讲>,深入透彻理解 MySQL 主从,GTID 相关技术知识.本文为学习记录,可能有误请谅解. 本文建议PC端观看,效果 ...
- 发布json数据_技术分享 | MySQL 8.0.17 GA 发布!
昨日 MySQL 官网正式发布 8.0.17 / 5.7.27 / 5.6.45 三个(维护)版本,距离上一个 GA 版本(8.0.16)发布时隔仅 88 天! MySQL 各开发团队的博客网站,同一 ...
- mysql内连接简写_技术分享 | MySQL 的 join_buffer_size 在内连接上的应用
本文详细介绍了 MySQL 参数 join_buffer_size 在 INNER JOIN 场景的使用,OUTER JOIN 不包含.在讨论这个 BUFFER 之前,我们先了解下 MySQL 的 I ...
最新文章
- tf.name_scope() 和 tf.variable_scope() 的用法和玄机
- glsl-BufferObject- change
- getpeername()函数
- 【功率控制】无线光通信-CDMA中闭环链路的功率控制MATLAB仿真
- 再上24天班,小长假就来了!
- ios解锁动态图片_iOS 苹果开机启动的 滑动来解锁动画
- web开发者工具,118页Vue面试题总结,涨姿势!
- LeetCode MySQL 1421. 净现值查询
- 【Python】Python库之图形用户界面
- 微信小程序开发:禁止输入表情的控制,验证方法,光标控制
- 反转?MIT女博士遭质疑:代码贡献太少,黑洞照片功臣另有其人
- 20120510,OSPF第三部分
- 【JSP开发】response重定向和resquest转发
- PHP 将微信录音arm格式文件转mp3格式
- fiddler使用详细教程
- 水准网测量平差matlab_基于MATLAB的测量平差计算
- 帝国cms模板 php代码 效率,帝国cms模板开发常用技巧总结
- CAS单点登录:CAS客户端搭建(整合Shiro和Spring Boot)
- 【MySQL】按各科成绩进行排序,并显示排名
- 机器人学——姿态描述方法(欧拉角,固定角,D-H法,绕定轴旋转)
热门文章
- 移动端设备标识码:DeviceID、IMEI、IDFA、UDID和UUID的名词解释
- 智能问答在金融领域中的实践与应用
- golang获取文件创建时间,最后访问时间,最后修改时间
- 如何深入浅出的理解数据仓库建模?
- 《Dream(梦想)》,无力的我,想放弃的我,深深的问自己,什么是梦想!!!
- hive启动mapreduce任务后,被killed
- 分布式文件系统之Tfs是什么?
- 多线程与高并发(五):强软弱虚四种引用以及ThreadLocal的原理与源码
- 分布式系统原理 之3 Lease机制
- java ehcahce刷新_Spring整合ehcache 注解实现查询缓存,并实现实时缓存更新或删除...