一次线上慢SQL调优分享

一次线上慢SQL调优分享

一周前,客户反馈做题页面经常卡顿,加载慢;我们监控比较少,所以根据直觉去MySQL慢查询日志一看,果然是一条慢SQL。废话不多,开整!!!

业务背景

一个在线做题的代码评测系统(类似牛客那种),每一次测试/考试(业务里叫 题目集),可以查询本次测试/考试的最近提交列表,如下图

用户的所有提交,都写入一张提交表,提交列表也是该表查询,表结构核心大致如下:

less 复制代码
 CREATE TABLE `submit_topic` (
   `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '提交ID',
   `topicset_id` int(11) NOT NULL COMMENT '题目集',
   `user_id` int(11) NOT NULL COMMENT '用户编号',
     /xxx
     //还包括 判题情况,分数等等
  
   PRIMARY KEY (`serial_number`),
   KEY `topicsetIndex` (`topicset_id`,`account_id`) USING BTREE COMMENT '题目集用户索引'
 ) ENGINE=InnoDB AUTO_INCREMENT=57157 DEFAULT CHARSET=utf8mb4;

SQL的样子

注意了,以下是重点!!!!!

因为可以查询题目集 指定用户的提交列表(默认是查所有用户),所以我还加了一个联合索引,

go 复制代码
  KEY `topicsetIndex` (`topicset_id`,`user_id`) USING BTREE COMMENT '题目集用户索引'

由于提交列表涉及到游标分页,所以默认查询最近提交列表 SQL大致是

vbnet 复制代码
 SELECT * FROM submit_topic as st
 //指定题目集ID
 where topicset_id = 34344   
 //根据主键ID游标分页查询
 and st.id > 12
 //因为ID递增,直接用ID排序
 ORDER BY st.id ASC
 limit 20;

自从上线SQL就这样,查询效率也可观。随便一个题目集一查 ,使用explain如下:

正常情况100毫秒内解决,这里要提一嘴,Using filesort是表示用到了排序,是文件还是内存排序,要看数据量

说好的慢SQL呢?

继续定位,发现只有考试那几场题目集响应慢。于是将topicset_id换一个数作为查询条件,SQL还是之前的

sql 复制代码
 explain SELECT * FROM submit_topic as st
 -- //换了一个题目集ID
 where topicset_id = 42
 -- //根据主键ID游标分页查询
 and st.id > 123443
 -- //因为ID递增,直接用ID排序
 ORDER BY st.idr ASC
 limit 20;
 ​

explain结果一看吓死人:

扫描行数达到7万多,另外几个 直接扫了几十万😫😫😫😫,跑了5秒才出结果。

原因分析

Extra使用了Using where,索引走的是主键ID

所以SQL会先把id = 123443的都扫出来,然后利用索引自动排序,然后返回Server过滤出 topicset_id=42的记录, 好家伙这相当于全表扫描了啊!!!

正常情况是会走 topicset_id这个索引的呀(即使用到了排序)

go 复制代码
  KEY `topicsetIndex` (`topicset_id`,`user_id`) USING BTREE COMMENT '题目集用户索引'

其实这个索引有个坑,就是 topicsset_id后面多了user_id,所以这个联合索引结合主键索引,类似于一个联合索引的效果,如下:

但是我们的SQL查询条件,没有用到user_id,这就导致主键ID索引不可用,所以 后面的排序 主键是用不到的。

如果一个topicset_id的提交数据太多,oder by id势必造成大量文件排序,这时 MySQL查询优化器认为排序代价太大,我干脆使用主键索引就避免排序,但而ID只有大于,使得扫描行数巨多,更要命的是要在如此多的数据里,Server层过滤出topicset_id = 42的记录。其次,由于Limit很小,优化器认为即使全表过滤也很快,然而事与愿违这几乎等于全表扫描!!!

问题解决

直接单独对 topicset_id设一个索引,根据MySQL索引下堆的原理,先通过topicset_id查询的主键是有序的,oder by不需要排序了。

最终SQL如下:

sql 复制代码
 explain SELECT * FROM submit_topic as st FORCE index(topicsetId)
 -- //指定题目集ID
 where topicset_id = 42
 -- //根据主键ID游标分页查询
 and st.serial_number > 3922
 -- //因为ID递增,直接用ID排序
 ORDER BY st.serial_number ASC
 limit 20;
 ​

Using index Condition 表示索引下堆,因为topicset_id选出来的 id是有序的,我们直接在存储引擎层过滤掉了数据。

效果还是杠杆的。

相关推荐
TT哇6 分钟前
*【每日一题 提高题】[蓝桥杯 2022 国 A] 选素数
java·算法·蓝桥杯
dessler21 分钟前
Docker-run命令详细讲解
linux·运维·后端·docker
火烧屁屁啦29 分钟前
【JavaEE进阶】初始Spring Web MVC
java·spring·java-ee
w_312345442 分钟前
自定义一个maven骨架 | 最佳实践
java·maven·intellij-idea
岁岁岁平安1 小时前
spring学习(spring-DI(字符串或对象引用注入、集合注入)(XML配置))
java·学习·spring·依赖注入·集合注入·基本数据类型注入·引用数据类型注入
武昌库里写JAVA1 小时前
Java成长之路(一)--SpringBoot基础学习--SpringBoot代码测试
java·开发语言·spring boot·学习·课程设计
Q_19284999061 小时前
基于Spring Boot的九州美食城商户一体化系统
java·spring boot·后端
张国荣家的弟弟1 小时前
【Yonghong 企业日常问题 06】上传的文件不在白名单,修改allow.jar.digest属性添加允许上传的文件SH256值?
java·jar·bi
ZSYP-S1 小时前
Day 15:Spring 框架基础
java·开发语言·数据结构·后端·spring