一次奇妙的线上的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拿来用,但是没仔细检查,很简单的优化点忘了。

相关推荐
东东5165 分钟前
OA自动化居家办公管理系统 ssm+vue
java·前端·vue.js·后端·毕业设计·毕设
程序员鱼皮20 分钟前
前特斯拉 AI 总监:AI 编程最大的谎言,是 “提效”
前端·后端·ai·程序员·开发
好好研究1 小时前
SpringBoot使用外置Tomcat
spring boot·后端·tomcat
索荣荣1 小时前
Spring Boot 实现DOCX转PDF(基于docx4j的轻量级开源方案)
spring boot·后端·pdf
mit6.8241 小时前
[todo]10个常见的后端框架
后端
没有bug.的程序员1 小时前
Spring Boot 与 Sleuth:分布式链路追踪的集成、原理与线上故障排查实战
java·spring boot·分布式·后端·分布式链路追踪·sleuth·线上故障排查
爱敲代码的憨仔2 小时前
Spring-AOP
java·后端·spring
短剑重铸之日2 小时前
《设计模式》第四篇:观察者模式
java·后端·观察者模式·设计模式
Hx_Ma162 小时前
SpringBoot注册格式化器
java·spring boot·后端
乔江seven2 小时前
【python轻量级Web框架 Flask 】1 Flask 初识
开发语言·后端·python·flask