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

相关推荐
惜鸟15 分钟前
Spring Boot项目自己封装一个分页查询工具
spring boot·后端
Dithyrambus15 分钟前
ObjectScript 中文入门教程
后端
林太白40 分钟前
也许看了Electron你会理解Tauri,扩宽你的技术栈
前端·后端·electron
松果集1 小时前
【Python3】练习一
后端
anganing1 小时前
Web 浏览器预览 Excel 及打印
前端·后端
肯定慧1 小时前
B1-基于大模型的智能办公应用软件
后端
TinyKing1 小时前
一、getByRole 的作用
后端
brzhang1 小时前
我们复盘了100个失败的AI Agent项目,总结出这3个“必踩的坑”
前端·后端·架构
郝同学的测开笔记2 小时前
云原生探索系列(十九):Go 语言 context.Context
后端·云原生·go