在MySQL中常常出現未按照理想狀況使用索引的狀況,今天記錄一種Order by語句的使用致使未按預期使用索引的狀況。測試
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
+----+-------------+-------+------------+-------+-------------------------------------------------------------+--------------------+---------+------+--------+----------+------------------------------------+ | 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 | +----+-------------+-------+------------+-------+-------------------------------------------------------------+--------------------+---------+------+--------+----------+------------------------------------+
+------------------+------------+-------------------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ | 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)
運行此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
從執行計劃狀況對比來看,使用createDate會進行額外的排序(Using filesort),這個不難理解。排序
使用force index (createDate)是能夠解決的,此方式上面已經測試過了索引
相似於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
將payDate 及createDate 添加爲組合索引,可是此舉不是一個好辦法,執行計劃也未按理想狀況運行。table
不管使用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)
也按預期的狀況正常。由此看來此方式相對以前的方案是最佳的。