一、前言
如题所示,博主在测试sql
的时候,发现有一条sql
的速度是固定的?因为是时间搜索,所以我们通过更改时间区间来测试如下:
sql
between '2018-05-20 00:00:00' and '2019-05-30 23:59:59 66646 rows 19s
between '2019-05-20 00:00:00' and '2019-05-30 23:59:59 9046 rows 19s
大概就是这个样子的,查询数据量在6W
和9千
的时候,速度竟然一样,而且是真的慢,这是为什么呢?刚发现时候还是挺郁闷的,不过通过这个问题也让博主学到不少东西,下面听我娓娓道来。
sql的来源是博主的上一篇文章: mysql使用group by实现组内排序实战
二、sql的explain对比
1、原sql的explain
sql
mysql> desc select max(pay_id) as max_pay_id,sum(pay_money) as total_pay_money,receiver_id from pay_info force index(receiver_id) where pay_date between '2019-05-20 00:00:00' and '2019-05-30 23:59:59' and order_state > 0 and server_unique_flag > 0 group by receiver_id;
+----+-------------+----------+-------+---------------+-------------+---------+------+---------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+-------+---------------+-------------+---------+------+---------+-------------+
| 1 | SIMPLE | pay_info | index | receiver_id | receiver_id | 8 | NULL | 2974170 | Using where |
+----+-------------+----------+-------+---------------+-------------+---------+------+---------+-------------+
大眼一瞟,用到了索引,extra
没有显示其他的东西,sql
优化看起来还不错,但是速度为何这么慢呢,难道是因为咱们强制指定索引的原因吗?
2、去掉force index强制索引
sql
mysql> desc select max(pay_id) as max_pay_id,sum(pay_money) as total_pay_money,receiver_id from pay_info where pay_date between '2019-05-20 00:00:00' and '2019-05-30 23:59:59' and order_state > 0 and server_unique_flag > 0 group by receiver_id;
+----+-------------+----------+-------+----------------------------------+----------+---------+------+--------+---------------------------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+----------+-------+----------------------------------+----------+---------+------+--------+---------------------------------------------------------------------+
| 1 | SIMPLE | pay_info | range | receiver_id,order_state,pay_date | pay_date | 5 | NULL | 109814 | Using index condition; Using where; Using temporary; Using filesort |
+----+-------------+----------+-------+----------------------------------+----------+---------+------+--------+---------------------------------------------------------------------+
大眼一瞟,用到了索引,但是extra
显示使用了临时表和文件排序,看起来是真难看。但是执行查询看了下,速度竟然提升到了3s
。这是为什么呢?明明看起来sql
很差劲的
3、分析结果
可以看到使用force index
之后,explain
的extra
确实是好看了很多,但是为什么查询比不用force
还慢呢?不加force
的话,extra
里面用到了临时表和文件排序呢。后来仔细看一下执行计划,用force_index
之后,sql
一共扫描了297W
的数据。而不加force
,一共扫描了10W
的数据。数据量的差异过大,所以说哪怕不加force
使用到了临时表也无所谓,速度也会更快一筹。
三、如何优化
1、force index 的问题吗
其实这里的主要问题并不是force index
这个语法的影响,而是force index
里面的索引列的影响。我们为了消除group by
的临时表和文件排序,所以选用了receiver_id
这个字段作为强制索引。但是我们忽略了索引关于筛选数据的作用。receiver_id
在这里基本没有起到忽略数据的作用,所以扫描行数是297W
。 这里我们使用force index(pay_date)
的话,扫描的函数会直线下降。
2、有人可能会说,加联合索引可以吗?
大家都知道,group by
会产生临时表,order by
会产生文件排序。根据sql
的执行顺序,先where
条件,后group by
,为啥不加个联合索引,让where
中的条件字段和group by
的字段都包含在这个索引中呢?这样group by
就不会使用临时表了。
答案是不可以 。首先我们要了解联合索引的最左原则,范围列可以用到索引(必须是最左前缀),但是范围列后面的列无法用到索引。同时,索引最多用于一个范围列,因此如果查询条件中有两个范围列则无法全用到索引。 我们sql
的where
条件里面有三个范围查询,所以加联合索引的话,也只能用到最左前缀的那个索引,而后续的字段是用不到这个联合索引的。group by
中的字段也用不了这个联合索引。 博主本地测试是这个样子的,如果大家有不同的看法,一定要提出来,大家一起讨论。
参考:blog.csdn.net/LJFPHP/arti...
四、最终选择
1、方案
erlang
1、使用force index消除临时表,避免扫描过多数据
2、选用能尽可能的筛选数据的列,使用这个列的索引
例如:
order_state缩小范围有限,缩小50%
server缩小范围10%
pay_date如果搜索范围小的话,能缩小到1%以下,如果搜索范围大,那速度慢点也正常,何况还能用到索引。因此选用pay_date
3、在扫描行数和extra优化之间选一个更合适的方案
4、选用force index(pay_date) 之后,耗时: 19s ---> 4s
5、给where 条件的字段都加联合索引(pay_date,order_state,server_unique_flag ,receiver_id) ,经过测试效率和单纯使用pay_date相当
2、测试效果
scss
force index(pay_date) 2018/5/20 4.01s 148w rows
force index(receiver_id) 2018/5/20 18.76s 297w rows
---------------------------------------------------------------
force index(pay_date) 2019/5/20 0.13s 12w rows
force index(receiver_id) 2019/5/20 18.63s 297w rows
博主这里最终选用的是用pay_date
这个时间索引,因为在sql
里面,时间筛选是最明显的,其他的where
字段辨识度都不是很高,不能起到很好的筛选效果。通过这个问题,博主才发现,原来并不是explain
看起来好看就行了,很多时候是要根据实际的情况来决定的,目标只有一个,那就是让sql快一点,再快一点。
3、优秀文章
(1) testerhome.com/topics/1650...
利用 group by
的 Loose Index Scan
优化 sql
(利用mysql
自带的松散索引扫描策略,实现优化的方案,我这边因为where
条件里面不是常量
,因此不能使用,但是思想很值得学习)
(2)blog.csdn.net/u014044812/...
(关于sql
和MySQL
的语句执行顺序)
优化水平还是有限,加油!
end