一次奇妙的线上的mysql调优分享

1.前情提要

小凯是一位程序员,今天写好了接口,和前端对接好,然后自测了几种特殊情况,都没问题,把功能发布上线了,然后开始思考今天晚上下班吃什么好呢。

凯哥!这个接口为什么会返回这么慢,前端姐姐说。小凯看了接口的返回信息是接口返回超时,是因为某个sql的执行时间太长导致后端已经没有任何数据返回了。

2.排查问题,遇到瓶颈

  1. 看看mysql是否还正常运行

    小凯打开了线上的项目,然后模拟了一下登录,发现登录正常,小凯松了口气,还好数据库没崩

  2. 查看服务器的内存和cpu的使用情况

    小凯登录服务器,用top命令查看了Java进程和mysql进程的cpu和内存的占比情况,以及整个服务器的磁盘资源是否还有剩余,发现各项占比都正常,服务器的资源都很充足。

  3. 看看是否是sql本身的问题

    确保服务没问题了以后,有问题的就是sql本身了。小凯把线上超时的sql复制,然后放到本地的测试环境中运行,发现响应时间是正常的,耗时100ms左右。小凯感觉很奇怪,然后登录线上的mysql,执行了查询命令,发现执行完成后耗时11s,奇了怪了,小凯又执行了几次,发现线上的响应时间确实很慢,小凯不知道怎么办了。

3.分析sql

小凯向同事小侯求助,侯哥,我这里有个问题,为啥线上的数据库搜索这么慢呢?侯哥说,你有没有尝试过用explain去分析,看看sql有没有走全表扫描。

小凯一拍脑袋,哎呀,咋忘了可以用explain来分析sql,之前的面试题都背到哪里去了。

小凯执行了explain,这是他执行得到的结果

线上环境

本地环境

小凯发现确实不一样,其中最大的区别在第三行

3.1 type 字段

type为All就是全表查询,type为ref就是普通索引查询 (其余的类型就请自行查阅了)

3.2 key + key_len + ref 字段

key代表是否用索引

key_len代表用索引的长度

ref代表MySQL在查找索引时,使用了哪些列或常量来与索引进行比较。

根据这三个字段,可以发现某一条sql本地用了索引,但是线上没有用索引。

3.3 rows 字段

rows 代表mysql所需要匹配的行数,也可以用来验证是否使用索引,这里发现线上要扫描17w行,而本地只要扫描50行,

4.尝试解决

根据explain分析,好像是线上的sql没有用到索引,但是本地用到了索引,会不会是线上的数据库没有添加索引的原因呢?

小凯登录了服务器,查看相关表的索引添加情况,发现线上和本地的索引添加完全相同,好像没有思路了。

5.真正解决

既然和索引的添加没有关系,那就从sql本身去分析,不考虑线上和本地的差异,看看这个sql还能不能优化。

这里把sql放出来,基本结构如下,简化了大部分的查询参数和排序和分组情况。实际上的sql比这个长很多,其中的一些敏感字段做了隐藏处理。

sql 复制代码
SELECT
  sub.process_name 
FROM 
(
  SELECT
    rsi.process_name
  FROM
    table1 AS rsi
    LEFT JOIN table3 AS pl ON pl.procedur_name = rsi.process_name 
  WHERE
    rsi.execution_mark = '1' 
  ) AS sub
  LEFT JOIN table2 AS rsif ON ( sub.production_order_number = rsif.production_order_number ) 
WHERE
  sub.order_number IN ('xxx1', 'xxx2' )

sql分析:实际上就是一个查询作为子表,然后把子表连接到另一张表上,最后汇总得到查询结果。

有问题的sql片段

sql 复制代码
  SELECT
    rsi.process_name
  FROM
    table1 AS rsi
    LEFT JOIN table3 AS pl ON pl.procedur_name = rsi.process_name 
  WHERE
    rsi.execution_mark = '1' 

写sql多的人可能已经发现问题了,子查询是有问题的。

这个查询会把全部的table1的数据一起查出来,仅靠execution_mark是排除不了多少数据的。如果table1的行数很多,那这个大数据的表去联表,然后作为大数据量的子表去连接,这个时间肯定会消耗很多,唯一用来查询的order_number被放在了最外面。

好的,发现问题,开始优化,把最后的查询order_number字段放到子查询就可以了。

优化完的sql如下

sql 复制代码
SELECT
  sub.process_name 
FROM 
(
  SELECT
    rsi.process_name
  FROM
    table1 AS rsi
    LEFT JOIN table3 AS pl ON pl.procedur_name = rsi.process_name 
  WHERE
    rsi.execution_mark = '1' and rsi.order_number IN ('xxx1', 'xxx2' )
  ) AS sub
  LEFT JOIN table2 AS rsif ON ( sub.production_order_number = rsif.production_order_number ) 

这样然后在线上执行以下explain分析

发现这个执行计划和本地的sql执行计划差不多了。

6.思考原因与总结

1.不同环境的差别

线上已经优化成和本地的速度差不多了。那说明本地的mysql自动帮我们优化了。本地的mysql版本是8.0,线上的mysql版本是5.7,mysql优化器应该检测到子查询后的条件可以直接放到里面,会节约查询时间,然后就对查询本身优化了,而mysql5.7没优化就导致查询慢了。

2.对sql优化不熟练 这算是个很常见的sql了,虽然是拿其他同事的sql拿来用,但是没仔细检查,很简单的优化点忘了。

相关推荐
WanderInk7 分钟前
依赖对齐不再“失联”:破解 feign/BaseBuilder 错误实战
java·后端·架构
LaoZhangAI2 小时前
GPT-4o mini API限制完全指南:令牌配额、访问限制及优化策略【2025最新】
前端·后端
LaoZhangAI2 小时前
FLUX.1 API图像尺寸设置全指南:优化生成效果与成本
前端·后端
Kookoos2 小时前
ABP VNext + EF Core 二级缓存:提升查询性能
后端·.net·二级缓存·ef core·abp vnext
月初,2 小时前
SpringBoot集成Minio存储文件,开发图片上传等接口
java·spring boot·后端
KubeSphere2 小时前
全面升级!WizTelemetry 可观测平台 2.0 深度解析:打造云原生时代的智能可观测平台
后端
Frank_zhou2 小时前
Tomcat - 启动过程:类加载机制详解
后端
bobz9652 小时前
kubevirt 使用图和节点维护计算|存储|网络
后端
屠龙少年想成恶龙2 小时前
jenkins的CICD部署
后端
满分观察网友z2 小时前
递归与迭代的优雅之舞:我在评论区功能中悟出的“树”之道(104. 二叉树的最大深度)
后端·算法