sql调优之:字符集不一致导致的索引失效案例

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群版 2核4GB 100GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用版 2核4GB 50GB
简介: 生产上有一段sql,大概几百行,做的是inser into …select ;的操作,但是执行了一个多小时都出不来,正常情况下不会这么久,通过一步步定位,最后发现是其中某一段的join 特别慢导致的,后面对这段sql重点分析发现,这里的表竟然没有走索引,对于几十万以上的表之间的连接,若是没有索引那就是灾难性的,加不加索引就好比一个人力三轮与超跑的区别。

一、问题复盘与解决



1.问题描述


生产上有一段sql,大概几百行,做的是inser into …select ;的操作,但是执行了一个多小时都出不来,正常情况下不会这么久,通过一步步定位,最后发现是其中某一段的join 特别慢导致的,后面对这段sql重点分析发现,这里的表竟然没有走索引,对于几十万以上的表之间的连接,若是没有索引那就是灾难性的,加不加索引就好比一个人力三轮与超跑的区别。至于为什么没走索引最后定位到了是因为字符集与排序规则与其他表不一致,所以导致了索引失效。下面是犯罪现场:

..........
left join 
 (
  select
   tmvrr.project_code,
   COUNT(1) as project_abnormal_sum, 
   COUNT(DISTINCT tmvrr.customer_id, tmvrr.room_relation_id) as customer_room_car_sum, 
   sum(case when tmvrr.rule_type = '1' then 1 else 0 end) as project_accuracy, 
   sum(case when tmvrr.rule_type = '2' then 1 else 0 end) as project_integrity, 
   sum(case when tmvrr.rule_type = '3' then 1 else 0 end) as project_timeliness, 
   case when tmvrr.estate_type in (1, 3) then '住宅' else '非住宅' end as estate_type  
  FROM
   sunacwy_mdm.tb_mdm_verification_rules_result tmvrr
  left join sunacwy_mdm.tb_unqualify_sc_pool tusp on 
   tmvrr.project_code = tusp.project_code
   and tmvrr.customer_id = tusp.mdm_data_id
   and tmvrr.rule_id = tusp.rule_codes
   and tmvrr.room_relation_id = tusp.mdm_data_id2
  where
   tmvrr.group_code <> ''
   and (tusp.project_code is null or tmvrr.commit_content <> tusp.commit_content)  
  group by
   tmvrr.project_code,
   case when tmvrr.estate_type in (1, 3) then '住宅' else '非住宅' end 
 ) tmvrr on 
  base_org.project_code  = tmvrr.project_code and base_org.estate_type   = tmvrr.estate_type
 .......


2.问题解决


问题解决其实很简单,就是将所有字段的字符集和排序规则更改成了mysql5.7的默认值(笔者生产库是mysql5.7),字符集是utf8mb4,排序规则是utf8_general_ci,然后问题就解决了,更改完以后只能说是天差地别。之前几个小时出不来,更改后几分钟就ok了。

-- 更改全表的字符集与排序规则,除了字符串其他字段其实无需指定排序规则,指定了也不生效
ALTER TABLE tb_mdm_verification_rules_result CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci ;
-- 单个字段的更改排序规则
alter table tb_mdm_verification_rules_result change group_name  group_name varchar(50) character set utf8mb4 collate utf8mb4_general_ci COMMENT '集团名称';


3.总结


其实产生原因很简单,但是找起来确实费了一番劲,这与explain使用不是这么熟练也是有关系的(当然最坑的是建表的人干得事)下面一节笔者会复现这个问题,并分析怎么排查这种问题,方便下次碰到一眼就可以解决。


二、mysql5.7与mysql8.0.3中问题复现



无论mysql5.7还是mysql8.0.3都具有相同的以下环境,我们就使用两个表的连接来复现该问题。

### 环境 预设###
表:tb_mdm_unit ,数据量:54280,索引字段:id
表:tb_mdm_floor,数据量:441849,索引字段:unit_id


1.mysql5.7环境问题复现


①相同字符集和排序规则时count


这里字符集都是utf8mb4,排序规则都是utf8mb4_general_ci

select count(1) from tb_mdm_unit un 
left join tb_mdm_floor fl on un.id = fl.unit_id ;


bf0cc80e73934da8ab618a8468562bf2.png


可以看到此时非常的快,只需要0.5s就出来结果了。


②看下相同字符集和排序规则的执行计划


07c0e8b64bbb41e3a6c11185a4168514.png


执行计划key列里是关联条件使用的索引,rows是索引关联到的行数(这个是效率高低的直观体现),Extra是非常重要的一列,sql真正底层在怎么运行是需要看这列的,Using index表示关联完全走了索引,是很高效的行为了。


③不相同字符集和排序规则count


alter table tb_mdm_floor change unit_id unit_id char(20) character set utf8 collate utf8_bin DEFAULT NULL COMMENT '单元id';
select count(1) from tb_mdm_unit un 
left join tb_mdm_floor fl on un.id = fl.unit_id ;


2315462b489e4ef5931de16021053421.png


可以看到此时是巨慢的,到达92s仍是没有出来结果,只能终止这个进程了。由此可见在更改了字符集和排序规则以后对sql的影响是灾难性的。


④看下不相同字符集和排序规则的执行计划


0e200161bf2240a88e4da068476b6e6e.png


对比这个执行计划与上面那个,可以发现 rows列和Extra列有很大区别,rows列的第二行,由6直接变成了435420,那这个数字意味着什么呢?这个问题先放下,可以先看下Extra列,这一列由Using index变成了Using where; Using index; Using join buffer (Block Nested Loop)。出现Using where 其实是因为mysql执行优化器对于这条sql进行了优化,优化后的sql 会在末尾拼接 “where 1”,在不走索引的查询中好像都会在末尾拼接这个。所以这里有个Using where(有兴趣的话,使用show warnings,即可看到这个执行优化器优化后的sql)。出现Using index是因为,确实走到了索引,但是因为字符集不一致即使走了也相当于没走,其实此时索引是不生效的。最后一个要说的也是最重要的是:Using join buffer (Block Nested Loop);当sql中出现外连时若是连接条件不是索引,Extra中就会提示这个提示(想要详细了解的建议看下这个博主的这篇文章写得还是不错的:MySQL Join原理),其实mysql5.5之前若是没有索引就是会出现笛卡尔积,mysql5.5之后8.0之前就是使用的Block Nested Loop来解决join时没有索引的问题,这种算法虽然比笛卡尔积效率高很多,但是对于大数据量的表来说还是无法承受的,所以上面的查询结果还是出不来,mysql8.0.19之后则采用了hash join 来解决连表时不走索引的问题。这种方式也很高效,效率其实是接近索引的。


2.mysql8.0环境问题复现


①相同字符集和排序规则时count


这里字符集都是utf8mb4,排序规则都是utf8mb4_general_ci(mysql8.0以后默认排序规则不是他了,这里为了验证保持统一)

select count(1) from tb_mdm_unit un 
left join tb_mdm_floor fl on un.id = fl.unit_id ;


下面是执行结果,可以看到0.49s很快就出来了


fc6b255be77c40958699338cc5c7727a.png


②看下相同字符集和排序规则的执行计划


7143c1ada8974e128c4da63b6709d001.png


Extra列都是Using index,说明连接完全走的索引,key列也没有区别,有点区别的是rows,可以发现第二行只有1,在5.7里这个数是6,不过这个区别和今天验证的问题没有关系,这个区别能说明,mysql8的索引比mysql5.7的索引更高效(所以建议能将mysql5.7升级到mysql8的就升级吧)。


③不相同字符集和排序规则count


alter table tb_mdm_floor change unit_id unit_id char(20) character set utf8 collate utf8_bin DEFAULT NULL COMMENT '单元id';
select count(1) from tb_mdm_unit un 
left join tb_mdm_floor fl on un.id = fl.unit_id ;

73076fdf0919411e928af5b4c72d37a5.png


在mysql8.0中将字符集更改过以后好像还是很快,只是比以前慢了一点,并没有像5.7那样根本出不来结果,这是为什么呢?其实mysql8以后(准确说是8.0.19以后)对不走索引的join进行了优化,如果么有索引那么就使用hash算法将等值连接的两个值都使用hash算法进行计算,然后使用计算后的结果进行连接(有点像是给你建了两个hash索引一样),这样也会提升很多的速度。具体看下执行计划。


④看下不相同字符集和排序规则的执行计划


c3b1e4d6223a41b582bdd9c478ae00ad.png


对比这个执行计划与mysql5.7同场景的执行计划可以发现,唯一的不同点就是 Using join buffer (Block Nested Loop) 变成了 Using join buffer (hash join)。上面也简单说了其实是mysql8对不走索引的等值连接进行的优化,等值连接的优化算法由Block Nested Loop变成了hash join。hash join 相当于是额外建立了两个hash索引,所以速度还是很快的,但是这个速度肯定不如走原来的索引快。


3.总结


left join 和right join 在 mysql5.7中若是连接条件不是索引,或者索引失效了,则会导致数据量的剧增,此时使用的连接算法BNL相当于是进行局部的笛卡尔积,效率也是很低的,到了mysql8.0以后,若是索引实现会使用hash算法对连接条件进行关联,这个效率是很高的,基本接近走索引的效率了。所以说升级到mysql8.0好处还是显而易见的(什么?msyql8.0还有什么优化?可以看看这一篇:关联子查询的前世今生),实在升级不了,那么我们就需要根据执行计划,来看连接表是否出现了BNL算法出现的情况,一旦出现请关注索引的建立和失效情况,不然sql的性能会非常低下。


4.延展问题:相同字符集不同排序规则会影响索引吗?


只调整排序规则,sql如下:

alter table tb_mdm_floor change unit_id unit_id char(20) character set utf8mb4 collate utf8mb4_polish_ci DEFAULT NULL COMMENT '单元id';
select count(1) from tb_mdm_unit un 
left join tb_mdm_floor fl on un.id = fl.unit_id ;


执行结果如下,直接报错,事实上笔者换了多个排序规则,都是会报这个错误,所以相同字符集不同排序规则会不会导致索引失效,直接就不用看了,因为sql执行不了。7f598810c838483fb532ab67ce0e038f.png


4.延展问题:字符集为什么导致索引失效?


其实很简单,两个字段不是使用字符编码,就没办法判断相等,建立的索引其实也是一个列,这个列的字符集合别的列不同,那就无法比较是否相等,自然就会造成索引失效了。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
1天前
|
SQL 监控 数据库
MSSQL性能调优实战:索引策略优化、SQL查询重写与高效并发管理的具体技巧
在Microsoft SQL Server(MSSQL)的性能调优过程中,索引策略的优化、SQL查询的重写以及高效并发管理是关键环节
|
1天前
|
SQL 运维 监控
MSSQL性能调优实战:索引精细化构建、SQL查询深度优化与高效并发控制策略
在Microsoft SQL Server(MSSQL)的运维与优化过程中,索引的精细化构建、SQL查询的深度优化以及高效并发控制策略是提升数据库性能的关键
|
1天前
|
SQL 运维 监控
MSSQL性能调优实战:索引策略优化、SQL查询重写与智能锁管理
在Microsoft SQL Server(MSSQL)的运维中,性能调优是确保数据库高效运行、满足业务需求的关键环节
|
1天前
|
SQL 监控 数据库
MSSQL性能调优深度探索:索引精细调整、SQL重写优化与智能并发控制
在Microsoft SQL Server(MSSQL)的日常管理和维护中,性能调优是一项至关重要的任务
|
1天前
|
SQL 运维 监控
MSSQL性能调优深度解析:索引优化策略、SQL查询优化技巧与高效并发管理实践
在Microsoft SQL Server(MSSQL)的运维与优化领域,性能调优是确保数据库高效运行、满足业务需求的关键环节
|
1天前
|
SQL 存储 监控
MSSQL性能调优深度解析:索引策略优化、SQL语句精炼与并发管理技巧
在Microsoft SQL Server(MSSQL)的性能调优领域,索引策略的优化、SQL语句的精炼以及高效的并发管理技巧是提升数据库性能不可或缺的三大方面
|
1天前
|
SQL 监控 Serverless
MSSQL性能调优实战:索引精细化构建、SQL查询深度优化与并发管理策略
在Microsoft SQL Server(MSSQL)的性能调优实践中,索引的精细化构建、SQL查询的深度优化以及高效的并发管理策略是提升数据库性能不可或缺的三大支柱
|
SQL Oracle 关系型数据库
SQL调优技巧:统计信息(文末福利)
统计信息类似于战争中的侦察兵,如果情报工作没有做好,打仗就会输掉战争。同样的道理,如果没有正确地收集表的统计信息,或者没有及时地更新表的统计信息,SQL的执行计划就会跑偏,SQL也就会出现性能问题。收集统计信息是为了让优化器选择最佳执行计划,以最少的代价(成本)查询出表中的数据。
2214 0
|
SQL 索引
生产sql调优之统计信息分析
今天凌晨,又被电话叫醒了,说是有1个sql,现在跑的很慢。问题已经挺严重了,想让我看看,能不能做点什么。 首先就是和他们确认最近有什么改动,他们说这个是用了很久的sql语句了,没有任何的改动,再听他们说,之前也没有任何的问题。
880 0