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

相关推荐
Warren9836 分钟前
如何在 Spring Boot 中安全读取账号密码等
java·开发语言·spring boot·后端·安全·面试·测试用例
David爱编程2 小时前
Java 内存模型(JMM)全景图:并发世界的底层基石
java·后端
知其然亦知其所以然3 小时前
SpringAI + Groq 实战:3 分钟教你搭建超快聊天机器人!
java·后端·openai
M1A13 小时前
诺贝尔奖得主的终极学习法:西蒙学习法全解读
后端
PetterHillWater3 小时前
基于AI互联网系统架构分析与评估
后端·aigc
MaxHua4 小时前
多数据源与分库分表方案设计
后端·面试
季风11324 小时前
17.Axon框架-消息
后端·领域驱动设计
苏三说技术4 小时前
Token续期的5种方案
后端
小森林84 小时前
分享一次Guzzlehttp上传批量图片优化的经历
后端·php
码事漫谈4 小时前
一文彻底搞懂缓存:从菜鸟到专家的完全指南
后端