美文网首页
记录一次慢sql排查

记录一次慢sql排查

作者: 叶迎宪 | 来源:发表于2020-11-09 22:23 被阅读0次

mysql的慢日志中,看到有这么一条

# Time: 201104 14:00:12
# User@Host: root[root] @  [172.17.0.143]  Id: 1729251
# Query_time: 12.155832  Lock_time: 0.000181 Rows_sent: 100  Rows_examined: 2652680
SELECT
 lu.userId,
 lu.userName,
 lu.photo userImage,
 lu.sex,
 wc.typeId AS courseType,
 wc.name AS courseName,
 lc.className,
 DATE_FORMAT(wcu.CreatedTime, '%Y-%m-%d %H:%i:%s') AS time
FROM
 wkt_courseclassuser wcu
INNER JOIN wkt_course wc on wcu.courseId = wc.id
INNER JOIN lxx_user lu ON wcu.userId = lu.userId
INNER JOIN (select t.* from (select * from lxx_classuserrecord WHERE classtypeid =1  ORDER BY CreateTime desc) t  GROUP BY t.userid ) lcur ON lcur.userid = lu.userId
INNER JOIN lxx_class lc on lc.classId = lcur.classId
INNER JOIN lxx_registerschool lr ON lr.schoolKey = lu.schoolKey
WHERE lc.status = 1 and lc.typeId = 1
 and lr.schoolId = 60800000000000001
 and wc.typeId in (1,2,3,23)
ORDER BY wc.CreateTime DESC
LIMIT 100;

不算太复杂的一条sql,但是扫了200多万行的数据,所以慢。先看执行计划

mysql> explain SELECT
-> lu.userId,
-> lu.userName,
-> lu.photo userImage,
-> lu.sex,
-> wc.typeId AS courseType,
-> wc.name AS courseName,
-> lc.className,
-> DATE_FORMAT(wcu.CreatedTime, '%Y-%m-%d %H:%i:%s') AS time
-> FROM
-> wkt_courseclassuser wcu
-> INNER JOIN wkt_course wc on wcu.courseId = wc.id
-> INNER JOIN lxx_user lu ON wcu.userId = lu.userId
-> INNER JOIN (select t.* from (select * from lxx_classuserrecord WHERE classtypeid =1 ORDER BY CreateTime desc) t GROUP BY t.userid ) lcur ON lcur.userid = lu.userId
-> INNER JOIN lxx_class lc on lc.classId = lcur.classId
-> INNER JOIN lxx_registerschool lr ON lr.schoolKey = lu.schoolKey
-> WHERE lc.status = 1 and lc.typeId = 1
-> and lr.schoolId = 60800000000000001
-> and wc.typeId in (1,2,3,23)
-> ORDER BY wc.CreateTime DESC
-> LIMIT 100;
+----+-------------+---------------------+--------+----------------------------------------------------------------------------------------------------------------+----------------------------------+---------+-------------------------+-------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------------+--------+----------------------------------------------------------------------------------------------------------------+----------------------------------+---------+-------------------------+-------+----------------------------------------------+
| 1 | PRIMARY | lr | ref | lxx_registerSchool_schoolId | lxx_registerSchool_schoolId | 9 | const | 1 | Using where; Using temporary; Using filesort |
| 1 | PRIMARY | lu | ref | PRIMARY,index_lxx_user_schoolKey | index_lxx_user_schoolKey | 603 | wkt_school.lr.schoolKey | 79 | Using where |
| 1 | PRIMARY | <derived2> | ref | <auto_key1> | <auto_key1> | 8 | wkt_school.lu.userId | 15 | Using where |
| 1 | PRIMARY | lc | eq_ref | PRIMARY | PRIMARY | 8 | lcur.classid | 1 | Using where |
| 1 | PRIMARY | wcu | ref | index_wkt_courseclassuser_courseId,index_wkt_courseclassuser_userId,index_wkt_courseclassuser_courseId_classId | index_wkt_courseclassuser_userId | 9 | wkt_school.lu.userId | 47 | Using where |
| 1 | PRIMARY | wc | eq_ref | PRIMARY | PRIMARY | 8 | wkt_school.wcu.courseId | 1 | Using where |
| 2 | DERIVED | <derived3> | ALL | NULL | NULL | NULL | NULL | 47204 | Using temporary; Using filesort |
| 3 | DERIVED | lxx_classuserrecord | ALL | NULL | NULL | NULL | NULL | 47204 | Using where; Using filesort |
+----+-------------+---------------------+--------+----------------------------------------------------------------------------------------------------------------+----------------------------------+---------+-------------------------+-------+----------------------------------------------+

乍一看好像最大的rows才47204,为什么实际执行扫描行数要大这么多呢?

网上找到一篇解释
https://dba.stackexchange.com/questions/73520/mysql-explain-has-different-row-count-than-slow-query-log

大概意思是,explain只是根据数据的特征,大概估算要扫描的行数,实际执行时,特别是需要做join操作时,结果集都是n*m的,因此实际执行结果可能要大很多。

看到执行计划最后两行,都是需要Using filesort的。很明显是产生于

INNER JOIN (select t.* from (select * from lxx_classuserrecord WHERE classtypeid =1 ORDER BY CreateTime desc) t GROUP BY t.userid ) lcur ON lcur.userid = lu.userId

一行。因为INNER JOIN的是一个子查询的结果,上面不会有索引,而且这个子查询的结果集也有几万条,开始的直观感觉是慢在这里。结果优化了很久,也没什么效果。最后把这个关联条件也去掉了,发现查询时间还是跟原来差不多,因此问题不是在此。

PS:第一次没有看懂explain的结果。explain中的第三行从derived2的结果中,也就是id为2的那条派生表查询中,自动建立了一个auto_key1的索引,因此inner join上面那行子查询并不会很慢

东找西找,发现去掉ORDER BY wc.CreateTime DESC以后,就变得很快了。查看了一下wkt_course的索引,果然CreateTime没有索引。赶紧补一下
CREATE INDEX index_wkt_course_CreateTime ON wkt_course(CreateTime)

然后再explain一下,

| 1 | PRIMARY | lr | ref | lxx_registerSchool_schoolId | lxx_registerSchool_schoolId | 9 | const | 1 | Using where; Using temporary; Using filesort |

第一行这里没有任何改观。实际执行起来也丝毫没有变快。

再静下来,仔细分析一下问题在哪里。mysql估计是先执行了连表查询,然后对这个结果集创建临时表,然后进行排序,最后在取出前100。用select count(*) 在去掉limit限制后数了一下,这个结果集有80多万条数据,怪不得排序很慢。这里总结出来一个经验,就是看explain首先要关注Using temporary,其次是Using filesort的问题。

要使ORDER BY的字段走索引,则需要让字段所在的表成为驱动表
https://blog.csdn.net/zerou8400/article/details/95389044

最终的解决方案,在order by的字段建立索引,并且使用straight_join,强制指定wkt_course为驱动表
SELECT
lu.userId,
lu.userName,
lu.photo userImage,
lu.sex,
wc.typeId AS courseType,
wc.name AS courseName,
lc.className,
DATE_FORMAT(wcu.CreatedTime, '%Y-%m-%d %H:%i:%s') AS time
FROM
wkt_course wc
straight_join wkt_courseclassuser wcu ON wcu.courseId = wc.id
INNER JOIN lxx_user lu ON wcu.userId = lu.userId
INNER JOIN lxx_registerschool lr on lr.schoolKey = lu.schoolKey
INNER JOIN (select t.* from (select * from lxx_classuserrecord WHERE classtypeid =1 ORDER BY CreateTime desc) t GROUP BY t.userid ) lcur ON lcur.userid = lu.userId
INNER JOIN lxx_class lc on lc.classId = lcur.classId
WHERE lr.schoolId = 60800000000000001
and wc.typeId in (1,2,3,23)
and lc.status = 1 and lc.typeId = 1
ORDER BY wc.CreateTime DESC
LIMIT 100;

围观一下优化后的执行计划
mysql> explain SELECT
-> lu.userId,
-> lu.userName,
-> lu.photo userImage,
-> lu.sex,
-> wc.typeId AS courseType,
-> wc.name AS courseName,
-> lc.className,
-> DATE_FORMAT(wcu.CreatedTime, '%Y-%m-%d %H:%i:%s') AS time
-> FROM
-> wkt_course wc
-> straight_join wkt_courseclassuser wcu ON wcu.courseId = wc.id
-> INNER JOIN lxx_user lu ON wcu.userId = lu.userId
-> INNER JOIN lxx_registerschool lr on lr.schoolKey = lu.schoolKey
-> INNER JOIN (select t.* from (select * from lxx_classuserrecord WHERE classtypeid =1 ORDER BY CreateTime desc) t GROUP BY t.userid ) lcur ON lcur.userid = lu.userId
-> INNER JOIN lxx_class lc on lc.classId = lcur.classId
-> WHERE lr.schoolId = 60800000000000001
-> and wc.typeId in (1,2,3,23)
-> and lc.status = 1 and lc.typeId = 1
-> ORDER BY wc.CreateTime DESC
-> LIMIT 100;
+----+-------------+---------------------+--------+----------------------------------------------------------------------------------------------------------------+------------------------------------+---------+-----------------------+-------+---------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+---------------------+--------+----------------------------------------------------------------------------------------------------------------+------------------------------------+---------+-----------------------+-------+---------------------------------+
| 1 | PRIMARY | wc | index | PRIMARY | index_wkt_course_CreateTime | 6 | NULL | 1 | Using where |
| 1 | PRIMARY | lr | ref | lxx_registerSchool_schoolId | lxx_registerSchool_schoolId | 9 | const | 1 | NULL |
| 1 | PRIMARY | wcu | ref | index_wkt_courseclassuser_courseId,index_wkt_courseclassuser_userId,index_wkt_courseclassuser_courseId_classId | index_wkt_courseclassuser_courseId | 9 | wkt_school.wc.id | 31 | Using where |
| 1 | PRIMARY | lu | eq_ref | PRIMARY,index_lxx_user_schoolKey | PRIMARY | 8 | wkt_school.wcu.userId | 1 | Using where |
| 1 | PRIMARY | <derived2> | ref | <auto_key1> | <auto_key1> | 8 | wkt_school.wcu.userId | 15 | Using where |
| 1 | PRIMARY | lc | eq_ref | PRIMARY | PRIMARY | 8 | lcur.classid | 1 | Using where |
| 2 | DERIVED | <derived3> | ALL | NULL | NULL | NULL | NULL | 36487 | Using temporary; Using filesort |
| 3 | DERIVED | lxx_classuserrecord | ALL | NULL | NULL | NULL | NULL | 36487 | Using where; Using filesort |
+----+-------------+---------------------+--------+----------------------------------------------------------------------------------------------------------------+------------------------------------+---------+-----------------------+-------+---------------------------------+

https://blog.csdn.net/m0_37894254/article/details/80675733

相关文章

  • 记录一次慢sql排查

    mysql的慢日志中,看到有这么一条 不算太复杂的一条sql,但是扫了200多万行的数据,所以慢。先看执行计划 m...

  • SQL优化以及锁表总结

    一、慢SQL的排查与分析 1、慢sql的定位使用Druid监控oracle的ARW文件mysql慢日志或使用命令s...

  • MySQL技术专题(7)优化方案-SQL 慢查询日志

    SQL排查[https://www.jianshu.com/writer] 慢查询日志: ** MySQL提供的一...

  • mysql 慢SQL分析

    开启慢SQL记录 为什么要开启慢SQL记录mysql在运行过程中,某些SQL可能会执行较长时间,我们通过配置一些东...

  • 【故障诊断】Mysql数据库CPU占用高

    运维同事监测到生产环境数据库CPU占用过高,业务系统反应卡慢,记录一下排查过程。 最后检查结果:非SQL性能导致的...

  • mysql慢查询日志

    慢查询日志 MySQL 慢查询日志是排查问题 SQL 语句,以及检查当前 MySQL 性能的一个重要功能,执行时间...

  • java面试题 --- MySQL③

    1. 生产环境中一条 SQL 执行特别慢,你要如何排查问题?可以查看 SQL 的执行计划。 2. 执行计划怎么用?...

  • 数据说话,日志说话

    记录下慢日志。 统计慢日志。 截图! 数据说话,有理有据! 有数据,也方便排查问题!

  • 后端存储8(处理慢SQL)

    慢 SQL的排查与防范 1,观察MySQL 的 CPU 利用率图,从监控图上看,故障时段 MySQL 的 CPU ...

  • MySQL性能优化(七)-- 慢查询

    1.慢查询的用途 它能记录下所有执行超过long_query_time时间的SQL语句,帮我们找到执行慢的SQL,...

网友评论

      本文标题:记录一次慢sql排查

      本文链接:https://www.haomeiwen.com/subject/cugrbktx.html