一次线上慢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 分钟前
Python 潮流周刊#78:async/await 是糟糕的设计(摘要)
后端·python·ai
YMWM_6 分钟前
第一章 Go语言简介
开发语言·后端·golang
路有瑶台6 分钟前
MySQL数据库学习(持续更新ing)
数据库·学习·mysql
y250810 分钟前
《Object类》
java·开发语言
曙曙学编程11 分钟前
初级数据结构——树
android·java·数据结构
BestandW1shEs17 分钟前
彻底理解消息队列的作用及如何选择
java·kafka·rabbitmq·rocketmq
爱吃烤鸡翅的酸菜鱼19 分钟前
Java算法OJ(8)随机选择算法
java·数据结构·算法·排序算法
码蜂窝编程官方22 分钟前
【含开题报告+文档+PPT+源码】基于SpringBoot+Vue的虎鲸旅游攻略网的设计与实现
java·vue.js·spring boot·后端·spring·旅游
lwprain32 分钟前
常用docker应用部署,wordpress、mysql、tomcat、nginx、redis
mysql·docker·tomcat
Viktor_Ye38 分钟前
高效集成易快报与金蝶应付单的方案
java·前端·数据库