MySQL group by 使用临时表导致慢查询问题定位
问题描述:拼团系统突然出现很多慢查询,(拼团规则表,Frule_id是主键,Fcreate_time不是索引,Fend_time是索引字段)
语句
select * from t_group_rule group by Frule_id order by Fcreate_time desc limit 0,10;
手动执行发现需要5s钟,可是全表只有70万行数据,即使全表扫描也要不了这么久啊,果断explain一把:
Extra显示用到了临时表和排序,可是group by后面是索引啊,为什么还要用到临时表,原因是group by 后又按照其他字段做了排序,因此需要将聚合结果放在临时表中,然后再根据Fcreate_time排序。
引申一下其他场景:
场景一:
select * from t_group_rule group by Frule_id order by Fend_time desc limit 0,10;
结果也一样的,因为Frule_id的排序和Fend_time的排序不一致,即group by执行完后是按照Frule_id有序,不是按照Fcreate_time有序的,因此必须将group by执行后的结果放到临时表中,再根据Fcreate_time排序。
场景二:
如果Frule_id和Fend_time是联合索引,那么肯定就不会用到临时表和排序。
场景三:
如果是order by NULL不做排序,或者按照Frule_id本身做排序,那肯定都不会用到临时表和排序:
结论:耗时主要用在了临时表上,而且临时表中数据比较大,达到70多万行。
优化方法:
(1)我们业务中Frule_id是递增的,因此按照Frule_id做降序即可。优化后语句:select * from t_group_rule group by Frule_id order by Frule_id desc limit 0,10; 耗时从5s降到0..01秒。
(2)如果必须按照非索引字段排序,可以利用where条件缩小查询范围,比如select * from t_group_rule where Fend_time > time1 and Fend_time < time2 group by Frule_id order by Frule_id desc limit 0,10; 这样临时表的数据就变少了,也能够减少耗时;
(3)使用联合索引,如果是group by Frule_id order by Fcreate_time,就将Frule_id和Fcreate_time建立联合索引。
第3种方法是从技术上解决问题,但是会比较受限,关键还是要根据我们自身业务,从业务优化去解决问题。