同事反应一个接口很慢,查询了一下,接口返回基本都在6秒以上,是有点慢了。一步步排除~~
一、访问其他接口都是正常,首先排除了是否服务器负载过高或者是网络堵塞。
二、查看接口代码,都是很简单的逻辑,没有for循环去查询数据库。
三、查看sql。把代码里面的sql拿到workbench中执行,每次都需要6秒以上,所以可以断定是sql出了问题。
1、sql如下:
SELECT pl.productName as name ,COUNT(pl.id) as count ,SUM(pl.money) as value
FROM product_log pl LEFT JOIN market m ON pl.marketId=m.id
WHERE 1=1 and m.category in(1,6)
GROUP BY
ORDER BY value DESC LIMIT 10
这个sql不长,查询的也只有两个表连接查询。查询,商户表(market)中类型为1以及6的商户,他们的销售流水(product_log)前十的销售产品,并且按照金额排序。
其中product_log表100W条数据,market表500多条数据,按道理讲这个数据量并不大。
2、先看下执行sql的执行计划
根据left join的定义
MySQL 表关联的算法是 Nest Loop Join,是通过驱动表的结果集作为循环基础数据,然后一条一条地通过该结果集中的数据作为过滤条件到下一个表中查询数据,然后合并结果
a.EXPLAIN 结果中,第一行出现的表就是驱动表
b.对驱动表可以直接排序,对非驱动表(的字段排序)需要对循环查询的合并结果(临时表)进行排序(Important!),即using temporary;
c.优化的目标是尽可能减少JOIN中Nested Loop的循环次数,以此保证:永远用小结果集驱动大结果集(Important!)!
第一,第一条执行计划,即table m。
(1)首先从执行计划来看,这条sql的确是小表驱动大表,小表才522条记录。符合执行计划优化原则。
(2)type第一条为ALL,全表扫描,这是最差的情况,这是可能可以优化的地方。
(3)key为null,代表这个查询没有使用到key,这是可能可以优化的地方。
(4)rows:表示MySQL根据表统计信息及索引选用情况,估算的找到所需的记录所需要读取的行数,这个需要结合起来看,因为在mysql里边是嵌套链接,所以,需要把所有rows相乘就会得到查询数据行关联的次数。因此这里是522*5577≈300W,要查询将近300W的行数,这是可能可以优化的地方。
(5)执行计划中出现了:Using temporary(使用临时表); Using filesort(文件查询),这些都是要优化的地方。
第二,第二条执行计划,即table pl
(1)这个执行计划是用了索引,唯一需要优化的地方就是rows。
优化方案:
1、首先想到的是优化sql,但是这个sql我能想到的优化方案有限。尝试用子查询去缩小结果集后再去SUM()、COUNT(),但是效果不理想,并且执行计划我看了和没有优化之前是一样的。
2、加索引。加索引有利弊,索引加快查询的速度,但是会使插入以及更新的速度变慢。对于写比较频繁的表,需要谨慎加索引。
(1)、按照加索引的规则,首先联合查询,在联合的字段加索引,我这里在
ON pl.marketId=m.id
加索引。pl表中对marketId字段加索引,market表id本身就是主键自带索引。加完后运行,发现速度并没有加快。就像上图我的执行计划一样。上图执行计划是加完这个所以后的效果。
(2)、在where条件后面加索引。market表是商户表,这个表不会经常修改变动,所以加索引不需要考虑太多写入、修改的因素。
WHERE 1=1 and m.category in(1,6)
where条件如上,所以索引加在category字段。
ALTER TABLE `market`
ADD INDEX `CATEGORY_IDX` (`category` ASC);
加完后,发现sql执行时间维持在2秒左右,提升了将近3倍。
执行计划如下图:
(3)考虑在group by以及sum()字段加索引
在
productName 以及 money 字段加索引
此处我使用复合索引
加完后,查询时间提升到了1.3秒左右。
前面说了加索引会引起写入、更新的性能下降。我的product_log不涉及更新,所以下面我将测试加索引与不加索引的写入性能区别
1、测试不加索引写入100W条数据性能
新建一个测试表test_no_index,字段与原先的product_log一致,不带索引,然后执行以下语句测试写入性能
INSERT INTO `test_no_index`
(`shopProductId`,`productId`,`marketId`,`unit`,`productName`,`amount`,`money`,`createTime`)
SELECT `shopProductId`,`productId`,`marketId`,`unit`,`productName`,`amount`,`money`,`createTime`
FROM `product_log`;
耗时如下:
Query OK, 1089969 rows affected (8.70 sec)
Records: 1089969 Duplicates: 0 Warnings: 0
每秒写入12W+条的性能,对于我们的业务肯定是足够了。
(2)接下来测试加索引后的写入性能。
新建一个测试表test_has_index,字段与原先的product_log一致。带索引。执行上面一样的sql,注意表名变一下。
耗时如下:
Query OK, 1089969 rows affected (15.72 sec)
Records: 1089969 Duplicates: 0 Warnings: 0
每秒7W的写入速度。