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

效果还是杠杆的。

相关推荐
爱勇宝6 分钟前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员
AskHarries22 分钟前
工具失败时怎么办:重试、回滚、人工确认和风险提示
后端·程序员
苏三说技术2 小时前
Claude Code从失控到起飞,只用了这些技巧
后端
长栎3 小时前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode3 小时前
Redis 在生产项目的使用
前端·后端
用户559822481223 小时前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode3 小时前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战3 小时前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha3 小时前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn3 小时前
Docker 容器管理入门 — 从镜像到容器编排
后端