ORDER BY致使索引使用不理想

在MySQL中常常出現未按照理想狀況使用索引的狀況,今天記錄一種Order by語句的使用致使未按預期使用索引的狀況。測試

1.  問題現象

1.1 SQL語句:

SELECT DISTINCT p.*  FROM tb_name p 
WHERE 1=1 AND p.createDate >= '2019-10-23'  AND p.createDate <= '2019-11-20 24:00:00'  AND p.status = '1'  AND p.areaName LIKE  '%上海%'  
 ORDER BY p.payDate DESC LIMIT 0 , 15

1.2 執行計劃以下:

+----+-------------+-------+------------+-------+-------------------------------------------------------------+--------------------+---------+------+--------+----------+------------------------------------+
| id | select_type | table | partitions | type  | possible_keys                                               | key                | key_len | ref  | rows   | filtered | Extra                              |
+----+-------------+-------+------------+-------+-------------------------------------------------------------+--------------------+---------+------+--------+----------+------------------------------------+
|  1 | SIMPLE      | p     | NULL       | range | createDate,idx_status_payDate                   | idx_status_payDate | 108     | NULL | 880063 |     0.74 | Using index condition; Using where |
+----+-------------+-------+------------+-------+-------------------------------------------------------------+--------------------+---------+------+--------+----------+------------------------------------+

1.3 表中索引信息以下:

+------------------+------------+-------------------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table            | Non_unique | Key_name                      | Seq_in_index | Column_name  | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+------------------+------------+-------------------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| tb_name          |          0 | PRIMARY                       |            1 | id           | A         |     1760103 |     NULL | NULL   |      | BTREE      |         |               |
| tb_name          |          1 | idx_payDate                   |            1 | payDate      | A         |     1734626 |     NULL | NULL   | YES  | BTREE      |         |               |
| tb_name          |          1 | createDate                    |            1 | createDate   | A         |     1736316 |     NULL | NULL   | YES  | BTREE      |         |               |
| tb_name          |          1 | idx_status_payDate            |            1 | status       | A         |           2 |     NULL | NULL   | YES  | BTREE      |         |               |
| tb_name          |          1 | idx_status_payDate            |            2 | payDate      | A         |     1741214 |     NULL | NULL   | YES  | BTREE      |         |               |
+------------------+------------+-------------------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
16 rows in set (0.00 sec)

1.4   理想狀況

運行此SQL耗時約5.7s。從SQL及索引狀況來看,使用createDate字段的索引應該會更好纔對,爲驗證此狀況,使用force index來強制使用createDate索引運行一次查看結果。優化

SQL改成以下:spa

SELECT DISTINCT p.*  FROM tb_name p  FORCE INDEX (createDate)
WHERE 1=1 AND p.createDate >= '2019-10-23'  AND p.createDate <= '2019-11-20 24:00:00'  AND p.status = '1'  AND p.areaName LIKE  '%上海%'  
ORDER BY p.payDate DESC LIMIT 0 , 15

修改後執行計劃以下:code

root@db09:03:13>explain SELECT DISTINCT p.*  FROM tb_namep  FORCE INDEX (createDate)
    -> WHERE 1=1 AND p.createDate >= '2019-10-23'  AND p.createDate <= '2019-11-20 24:00:00'  AND p.status = '1'  AND p.areaName LIKE  '%上海%'  
    ->  ORDER BY p.payDate DESC LIMIT 0 , 15;
+----+-------------+-------+------------+-------+---------------+------------+---------+------+--------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key        | key_len | ref  | rows   | filtered | Extra                                              |
+----+-------------+-------+------------+-------+---------------+------------+---------+------+--------+----------+----------------------------------------------------+
|  1 | SIMPLE      | p     | NULL       | range | createDate    | createDate | 6       | NULL | 117858 |     1.11 | Using index condition; Using where; Using filesort |
+----+-------------+-------+------------+-------+---------------+------------+---------+------+--------+----------+----------------------------------------------------+
1 row in set, 3 warnings (0.00 sec)

實際運行該SQL耗時約爲0.15s,相差約50倍的差距。blog

1.5 簡單分析

從執行計劃狀況對比來看,使用createDate會進行額外的排序(Using filesort),這個不難理解。排序

 

2   各類不太合理嘗試

2.1 強制使用索引

使用force  index (createDate)是能夠解決的,此方式上面已經測試過了索引

2.2  忽略不理想的索引

相似於force index,可使用IGNORE INDEX ,其實目的也在於使用上createDate 索引,例如:it

 

SELECT DISTINCT p.*  FROM tb_name p  IGNORE INDEX (idx_status_payDate,idx_payDate)
WHERE 1=1 AND p.createDate >= '2019-10-23'  AND p.createDate <= '2019-11-20 24:00:00'  AND p.status = '1'  AND p.areaName LIKE  '%上海%'  
ORDER BY p.payDate DESC LIMIT 0 , 15

 

其效果和force index 一致,運行耗時也在0.15s左右。io

2.3 添加組合索引

將payDate 及createDate 添加爲組合索引,可是此舉不是一個好辦法,執行計劃也未按理想狀況運行。table

 

3.  相對合理的方式

不管使用force  index  仍是 ignore index都會影響MySQL優化器自身的執行狀況。例如createDate 若是範圍很大,那麼其實走payDate 的索引取前15條記錄會更快,爲了讓應用改動最少且不會由於其餘條件的變化而致使未能走合理的索引,選擇另外一種優化方案,將SQL改成以下狀況:

SELECT DISTINCT p.*  FROM tb_name p 
WHERE 1=1 AND p.createDate >= '2019-10-23'  AND p.createDate <= '2019-11-20 24:00:00'  AND p.status = '1'  AND p.areaName LIKE  '%上海%'  
 ORDER BY p.payDate DESC, createDate LIMIT 0 , 15

此時執行執行計劃以下:

+----+-------------+-------+------------+-------+-------------------------------+------------+---------+------+--------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type  | possible_keys                 | key        | key_len | ref  | rows   | filtered | Extra                                              |
+----+-------------+-------+------------+-------+-------------------------------+------------+---------+------+--------+----------+----------------------------------------------------+
|  1 | SIMPLE      | p     | NULL       | range | createDate,idx_status_payDate | createDate | 6       | NULL | 123024 |     5.55 | Using index condition; Using where; Using filesort |
+----+-------------+-------+------------+-------+-------------------------------+------------+---------+------+--------+----------+----------------------------------------------------+
1 row in set, 3 warnings (0.00 sec)

調整createDate 以後,執行執行計劃:

root@db 09:51:00>EXPLAIN 
    -> SELECT DISTINCT p.*  FROM tb_name p   IGNORE INDEX (idx_status_synIs_deleteStatus)
    -> WHERE 1=1 AND p.createDate >= '2009-10-23'  AND p.createDate <= '2019-11-20 24:00:00'  AND p.status = '1'  AND p.areaName LIKE  '%上海%'  
    ->  ORDER BY p.payDate DESC,createDate DESC  LIMIT 0 , 15;
+----+-------------+-------+------------+------+-------------------------------+--------------------+---------+-------+--------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys                 | key                | key_len | ref   | rows   | filtered | Extra                                              |
+----+-------------+-------+------------+------+-------------------------------+--------------------+---------+-------+--------+----------+----------------------------------------------------+
|  1 | SIMPLE      | p     | NULL       | ref  | createDate,idx_status_payDate | idx_status_payDate | 108     | const | 880205 |     5.56 | Using index condition; Using where; Using filesort |
+----+-------------+-------+------------+------+-------------------------------+--------------------+---------+-------+--------+----------+----------------------------------------------------+
1 row in set, 3 warnings (0.00 sec)

也按預期的狀況正常。由此看來此方式相對以前的方案是最佳的。

相關文章
相關標籤/搜索