当前位置: 首页 > news >正文

MySQL中的SQL优化建议那么多,该如何有的放矢

这是学习笔记的第 2158 篇文章


640?wx_fmt=gif

  今天早上看到同事的一个优化需求,优化的时间其实不多,但是对于这条SQL的优化思考了很多,希望有一些参考。

  业务同学提供的SQL如下:

SELECT 	
b.order_id	
FROM	
(	
SELECT	
a.order_id,	
a.order_time AS create_time	
FROM	
trade_order a	
WHERE	
a.user_id = 12345678	
。。。。。。	
AND a.deleted = 0	
UNION	
SELECT	
v.order_id,	
v.create_time	
FROM	
virtual_order v	
WHERE	
v.user_id = 12345678	
。。。。	
ORDER BY	
order_id DESC	
) AS b	
LIMIT 0,	
 10;

根据反馈,这条SQL的执行时长在200毫秒,在压测情况下会到500毫秒左右,从业务层面来看,目前是不满足需求的,想看看我们有没有优化的建议。 

第一印象这条SQL执行时长200~500毫秒,要优化好像可打的牌不多啊,如果要想得到一个可接受的基准值,当然反馈会是越快越好。所以从这个角度来看,我们不妨按照毫秒级优化的标准来看,这条SQL需要做哪些补充的工作。

首先通过SQL看下逻辑情况,整体的逻辑是按照用户id去查询两个数据源(trade_order和virtual_order),从两个数据源查询出10条单号数据返回。这个用户在两个数据源中可能有单号,也可能没有,只要有匹配的就返回,累计返回10条,看起来是为了去重才选择了union的组合方式。

先不看表结构信息,我大体有了如下的建议:

  1. union的模式更建议采用union all,两个数据源存在数据重合应该是不合理的。

  2. 查询语句里面使用了order_time但是数据返回压根没有用到,建议去掉

  3. SQL层面承载了太多的数据处理压力,比如多数据源,去重和过滤,分页,是不是可以做下精简。 

    当然到了这里,和业务的需求就产生了脱节,这就属于那种看啥都不顺眼的状态,总想找出点问题来,而且对于业务同学来说,哪怕十个八个需求,你得有一个需求的收益更高,他们采用其他需求的可能性才越大,否则就是不作为了

所以到了这里,我们开始做下分析,要优化SQL不看看执行计划是不过关的,在执行前,我的大体感觉表数据量很大,应该是生成了派生表,然后在数据去重过滤层面的消耗比较大,而两个子查询来说,返回的结果集应该很少。 预测的执行情况是:

1)子查询trade_order应该很快,毫米级响应

2)子查询virtual_order应该也很快,但是最后有一个order by操作,可能代价略高

3)union的去重过滤代价相对较大,涉及到两个结果集的合并,如果返回结果较多,可能是瓶颈

从执行结果来看,让我有些意外,其中virtual_order的返回结果竟然有40多万行,相当于直接走了全表扫描。

640?wx_fmt=png

而其他的部分也会收到相关影响,所以后续的处理都会受到影响。 

为了快速定位问题,我把两个子查询拆开单独执行,查看执行计划,这是分析瓶颈最快的一种处理思路。 

 

>>explain SELECT	
    -> v.order_id,	
    -> v.create_time	
    -> FROM	
    -> virtual_order v	
    -> WHERE	
    -> v.user_id = 12345678	
    。。。;

执行计划如下:

640?wx_fmt=png

可以看到是直接走了全表扫描,这是一个基础需求,不会业务同学漏了索引吧,然后查看表结构:

 CREATE TABLE `virtual_order` (	
  `order_id` varchar(255) NOT NULL COMMENT '订单ID',	
。。。	
  `user_id` varchar(255) DEFAULT NULL COMMENT '用户ID',	
。。。	
  `refund` tinyint(3) DEFAULT NULL COMMENT ' 是否退款(1:无,2:是)',	
  `atc_pay_status` int(3) NOT NULL DEFAULT '0' COMMENT '支付状态',	
。。。	
  PRIMARY KEY (`order_id`),	
  KEY `order_status` (`order_status`),	
  KEY `user_id` (`user_id`),	
  KEY `prepaid_account` (`prepaid_account`)	
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

发现user_id是走了索引的,那么问题来了,user_id既然是索引,但是为什么SQL语句中依然走了全表扫描呢?

此处思考10秒钟,继续往下看。  

其实这个时候问题的边界都很清晰了,SQL语句很简单,索引也存在,走了全表扫描,在MySQL中可以暂时排除直方图的影响,目前在5.7版本中还不存在直方图的特性,那么结果只有一个:字段的类型产生了隐式类型转换。

这个部分可以参考这篇的一篇文章

比如初始化语句如下:

create table test(id int primary key,name varchar(20) ,key idx_name(name));	
insert into test values(1,'10'),(2,'20');

然后我们使用如下的两条语句进行执行计划的对比测试。

 explain select * from test where name=20;	
 explain select * from test where  name=’20’;

在name列为字符类型时,得到的执行计划列表如下:

执行计划列

Where条件: name=20

where条件: name='20'

           id:

1

1

 select_type:

SIMPLE

SIMPLE

 partitions:

NULL

NULL

 type:

index

ref

possible_keys:

idx_name

idx_name

 key:

idx_name

idx_name

 key_len:

63

63

 ref:

NULL

const

 rows:

2

1

 filtered:

50

100

 Extra:

Using where; Using index

Using index


3 warnings (0.00 sec)

1 warning (0.00 sec)

可以很明显的看到,在name为字符串类型时,如果where条件为name=20,则执行全索引扫描,查看warning信息会明确提示:

Message: Cannot use range access on index 'idx_name' due to type or collation conversion on field 'name'

所以此处的问题也显而易见了。 

修改了子查询的条件为字符后,整个SQL的执行效率就立马好多了。 

使用sql_no_cache的方式测试。 

SQL修改前性能:

+-----------------------+2 rows in set (0.27 sec)

修改后性能:

+-----------------------+2 rows in set (0.00 sec)

然后再次查看执行计划,就都规规矩矩了,这样我们就解决了瓶颈问题,而那些规范,更好的改进就可以逐步展开了,而从建议的角度来看,采用的概率也会高一些

640?wx_fmt=png

当然在这个基础上确实有一些补充的建议,在定位瓶颈之后也可以摊开来说了。

优化不是一锤子买卖,在这个基础上,也发现了一些其他的问题,可以看下这个表的表结构信息,其实能够发现一些设计上的小问题。 

1) 表字段的字符型基本都是varchar(255),需要尽可能避免这种使用习惯,对于存储性能的开销会有显著影响

2)使用的int类型 int(3),这种使用对于int还是存储4个字节,但是有限范围大大减少,可以考虑更小的数值类型

3)表的索引比较松散,可以根据业务模型创建复合索引,比如user_id和status的结合场景更多,应该创建的是(user_id,status)的复合索引

近期热文:

转载热文:

相关文章:

  • dbaplus广州站归来
  • 梳理这件事情做不好,很多努力都是白费
  • 怎么证明根号2是无理数,我们来推导和计算,还有逼格极高的算法
  • 行锁:InnoDB 替代 MyISAM 的重要原因
  • 数据双向复制中的6个数据冲突场景和解决思路
  • MySQL多活数据消费服务设计方案
  • 数学有趣地超乎你的想象
  • MySQL中10多张表关联要做优化,怎么理解逻辑幂等
  • 回答:我不小心把公司的数据库给删了,该不该离职?
  • 医院门诊透视的生活万花筒
  • 图数据库Neo4j学习笔记系列
  • MySQL的主键命名挺任性,就这么定了
  • 运维开发和测试中常见的8个问题
  • MySQL数据清理有技巧,这么破
  • 《奇葩说》救猫还是救画,我做了认真的分析
  • Druid 在有赞的实践
  • HashMap ConcurrentHashMap
  • laravel with 查询列表限制条数
  • macOS 中 shell 创建文件夹及文件并 VS Code 打开
  • php ci框架整合银盛支付
  • Redis学习笔记 - pipline(流水线、管道)
  • Spark RDD学习: aggregate函数
  • UMLCHINA 首席专家潘加宇鼎力推荐
  • 当SetTimeout遇到了字符串
  • 订阅Forge Viewer所有的事件
  • 工作手记之html2canvas使用概述
  • 记录一下第一次使用npm
  • 设计模式(12)迭代器模式(讲解+应用)
  • 使用Envoy 作Sidecar Proxy的微服务模式-4.Prometheus的指标收集
  • 使用Swoole加速Laravel(正式环境中)
  • 通过获取异步加载JS文件进度实现一个canvas环形loading图
  • 基于django的视频点播网站开发-step3-注册登录功能 ...
  • #Linux(Source Insight安装及工程建立)
  • #大学#套接字
  • (aiohttp-asyncio-FFmpeg-Docker-SRS)实现异步摄像头转码服务器
  • (C#)一个最简单的链表类
  • (C语言)逆序输出字符串
  • (DFS + 剪枝)【洛谷P1731】 [NOI1999] 生日蛋糕
  • (echarts)echarts使用时重新加载数据之前的数据存留在图上的问题
  • (pytorch进阶之路)CLIP模型 实现图像多模态检索任务
  • (Redis使用系列) Springboot 使用Redis+Session实现Session共享 ,简单的单点登录 五
  • (二)springcloud实战之config配置中心
  • (二十四)Flask之flask-session组件
  • (附源码)spring boot公选课在线选课系统 毕业设计 142011
  • (附源码)springboot家庭财务分析系统 毕业设计641323
  • (一)u-boot-nand.bin的下载
  • (转)人的集合论——移山之道
  • (转载)利用webkit抓取动态网页和链接
  • ./和../以及/和~之间的区别
  • .NET 4.0中的泛型协变和反变
  • .net core 6 redis操作类
  • .NET delegate 委托 、 Event 事件
  • .NET delegate 委托 、 Event 事件,接口回调
  • .net MySql
  • .net 按比例显示图片的缩略图