问题
上周客户反馈有个功能比较卡,经同事排查是因为一个SQL语句查询比较慢导致的:
SELECT
am.article_id,
am.simhash
FROM
`article_mark` AS am
INNER JOIN `article_distribute_relation` AS adr ON am.article_id = adr.article_id
WHERE
am.STATUS = 0
AND am.system_type =3
AND adr.distribute_id IN (
SELECT `id` FROM `article_distribute`
WHERE `mark_status` = 0 AND `created_at` >= "2022-08-13" AND `is_deleted` = 0
)
GROUP BY
am.article_id
其中adr和am表的记录数大概都是400到500万,而ad表的数据量大概只有三四千。
解决过程
一开始看到只有几百万的数据量就慢查询了,会不会是left join的时候没有用上索引。确认am和adr表的article_id字段,确实都是有索引的,但是他们的字段类型却不太一样,一个是char(24),而另一个是varchar(24)。
都改成char(24),发现没什么效果,看来MySQL也不会那么傻。
explain看看这个语句的执行计划:
从结果看,索引都已经用上了,本来以为rows值(需要扫描的记录数)应该很大,但是看结果也不只有几百,虽然ad表的Extra有“Using index condition; Using where; Using temporar...”,但是这个表的数据量很少,应该不会太影响才对。
这个语句命中的记录行数确实是比较多的(3.6万多),但是返回的两个字段都是业务需要的。一时没了主意,想了一些准备绕过这个问题的解决方案。后来想起phpmyadmin是可以分析sql语句的性能(MySQL profile),分析结果如下:
显然,绝大部分时间都花在了sending data上,眼前一亮,肯定是返回的数据太多或者中间过程的数据量太多,这里还有一个group by,这个可能也是很耗时间的(之前也想过这里,不过当时没有多想,因为group by的字段是有索引的)。
于是去掉group by,用上distinct,分析性能:
从秒级下降到了毫秒级,完美!
在不少情况下,是可以使用distinct来替换group by的,性能会大大的提升。
不妨再回头看一下换成distinct之后的执行计划:
可以看到,和之前是完全一致的。
优化MySQL查询语句,单靠explain是不够的,profile往往是更加有效的方法。