一次线上慢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是有序的,我们直接在存储引擎层过滤掉了数据。

效果还是杠杆的。

相关推荐
雷神乐乐4 分钟前
File.separator与File.separatorChar的区别
java·路径分隔符
小刘|9 分钟前
《Java 实现希尔排序:原理剖析与代码详解》
java·算法·排序算法
逊嘘28 分钟前
【Java语言】抽象类与接口
java·开发语言·jvm
morris13135 分钟前
【SpringBoot】Xss的常见攻击方式与防御手段
java·spring boot·xss·csp
monkey_meng1 小时前
【Rust中的迭代器】
开发语言·后端·rust
余衫马1 小时前
Rust-Trait 特征编程
开发语言·后端·rust
monkey_meng1 小时前
【Rust中多线程同步机制】
开发语言·redis·后端·rust
七星静香1 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
Jacob程序员1 小时前
java导出word文件(手绘)
java·开发语言·word
ZHOUPUYU1 小时前
IntelliJ IDEA超详细下载安装教程(附安装包)
java·ide·intellij-idea