1. 问题描述:早上刚来上班,业务部门同事反应管理后台无法登录
2. 问题排查定位
2.1 服务器排查
a. 接口是否可以调通:首先自己登陆后台,发现时好时坏,偶尔接口返回【系统忙】。我们系统接口异常调不通会返回系统忙
b. 服务是否死掉或者假死:连接服务器->查看Java进程,服务正常;排除假死:重启该服务,接口依然调不通
c. 服务器各项指标:登录阿里云->云服务器ECS->监控,查看CPU、内存、系统负载、云盘iops、网络带宽、连接数,各项指标使用率均在正常范围
2.2 数据库排查
a. 数据库各项指标:登录阿里云->云数据库RDS->监控与报警,查看CPU、内存、磁盘空间、iops、网络带宽、连接总数,其它指标正常,唯独CPU使用率达到100%!
b.一键诊断:登录阿里云->云数据库RDS->自治服务->一键诊断,发现CPU使用率偏高,出现了异常慢SQL
c. 慢日志:日志管理 -> 慢日志明细 ,发现有SQL语句耗时7~8分钟,甚至20~30分钟。一条SQL语句执行20多分钟!!!
d. sql分析:
SELECT you.nick_name,you.mobile,ycu.createtime,ycu.id,ycu.ym_userid, ycu.`status`,cir.coupon_name,cir.use_price,if(ycu.status = '1',ycul.createtime,'') as usedtime,cir.coupon_type as couponType, cir.price,cir.province,ycu.end_time from ym_coupon_user ycu LEFT JOIN ym_org_user you on ycu.ym_userid = you.id LEFT JOIN coupon_issue_record cir on ycu.coupon_issue_record_id = cir.id LEFT JOIN ym_coupon_used_log ycul on ycu.id = ycul.ym_coupon_user_id where 1=1 and you.platform_code ='lngpt' order by ycu.createtime desc limit 10
可以看出是四表联查,如果有数据量比较大的表,然后关联字段又没有索引将会非常耗时
用explain分析,即在SQL语句前加上关键字 EXPLAIN然后执行:
可以看出,4个表中主表全盘扫描,第2、3表关联字段有唯一索引只扫描一行,但是第4张表也进行了全盘扫描。上图是测试环境,数据量较小。
线上数据表1和表4分别由10多万条和4万多条!然后关联字段没有加索引,导致查询异常慢。
给表4的关联字段添加索引(ym_coupon_user_id) 后执行EXPLAIN:
可以看出,表4(ycul)也用上了索引,只取出了一行。到此问题得以解决,一条查询语句耗时由20多分钟优化到0.16秒~
3. 总结:多表关联查询,如果数据量较大查询太慢,被驱动表的关联字段要加索引。
SQL语句剖析:
对比加索引前后的 EXPLAIN 结果分析:
1. ycu表在ycul表未加索引前使用了临时表 (using temporary)
2. ycul表未加索引是:Using where; Using join buffer (Block Nested Loop),加了索引后是:Using where
关键字:using temporary、Using join buffer (Block Nested Loop)、Using filesort
下面分别进行解析:
问题1.为啥会出现临时表
答案:用非驱动表字段排序会出现临时表
关于左关联查询:首先分驱动表和被驱动表,先去驱动表查询数据,然后以此数据为基准去查询被驱动表。
驱动表和被驱动表的选择受MySQL优化器的控制,这个比较复杂:
a、没其它条件的话,选择结果集小的作为驱动表
b、优先以order by字段所在的表作为驱动表,即时该表结果集较大
c、即使符合上面的条件,优化器还会优先选择关联字段有索引的表作为 被驱动表
d、都有索引,则不一定会选择order by字段的表作为驱动表,此时如果数据量差距大选择数据量小的为驱动表,如果数据量差距不明显则选择order by字段的表为驱动表
**** 到此,上面那条SQL语句就可以明白了,我们再分析下 EXPALIN ,为了方便查看我把SQL语句和EXPLAIN再复制一遍:
SELECT you.nick_name,you.mobile,ycu.createtime,ycu.id,ycu.ym_userid, ycu.`status`,cir.coupon_name,cir.use_price,if(ycu.status = '1',ycul.createtime,'') as usedtime,cir.coupon_type as couponType, cir.price,cir.province,ycu.end_time from ym_coupon_user ycu LEFT JOIN ym_org_user you on ycu.ym_userid = you.id LEFT JOIN coupon_issue_record cir on ycu.coupon_issue_record_id = cir.id LEFT JOIN ym_coupon_used_log ycul on ycu.id = ycul.ym_coupon_user_id where 1=1 and you.platform_code ='lngpt' order by ycu.createtime desc limit 10
首先,当ycul表关联字段没加索引时,表ycu和表ycul都没有索引,虽然order by ycu.createtime,但ycu和ycul数据量差别很大,所以小表ycul被优化器选为驱动表。
而此时order by ycu.createtime,即排序字段在非驱动表上,迫使MySQL使用了临时表,结果可想而知。
当给ycul的ym_coupon_user_id加上索引,则优化器选择了ycu为主表,order by字段在主表,就没有临时表了。
问题2. 什么时候会使用 join buffer
…………待续
问题3. 何时出现 Using filesort
当Query中包含 order by 操作,而且无法利用索引完成的排序操作称为“文件排序”