2016-03-09 52 views
1

我試圖優化我的查詢,但是,MySQL似乎在查詢中使用了非最佳索引,我似乎無法弄清楚什麼是錯誤的。我的查詢如下:爲什麼MySQL不使用最佳索引

SELECT SQL_CALC_FOUND_ROWS deal_ID AS ID,dealTitle AS dealSaving, 
     storeName AS title,deal_URL AS dealURL,dealDisclaimer, 
     dealType, providerName,providerLogo AS providerIMG,createDate, 
     latitude AS lat,longitude AS lng,'local' AS type,businessType, 
     address1,city,dealOriginalPrice,NULL AS dealDiscountPercent, 
     dealPrice,scoringBase, smallImage AS smallimage,largeImage AS image, 
     storeURL AS storeAlias, 
     exp(-power(greatest(0, 
      abs(69.0*DEGREES(ACOS(0.82835377099147 * 
       COS(RADIANS(latitude)) * COS(RADIANS(-118.4-longitude)) + 
       0.56020534635454*SIN(RADIANS(latitude)))))-2), 
         2)/(5.7707801635559)) * 
      scoringBase * IF(submit_ID IN (18381), 
       IF(businessType = 1,1.3,1.2),IF(submit_ID IN (54727),1.19, 1) 
         ) AS distance 
    FROM local_deals 
    WHERE latitude BETWEEN 33.345362318841 AND 34.794637681159 
     AND longitude BETWEEN -119.61862872928 AND -117.18137127072 
     AND state = 'CA' 
     AND country = 'US' 
    ORDER BY distance DESC 
    LIMIT 48 OFFSET 0; 

清單索引的表顯示:

+-------------+------------+-----------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 
| Table  | Non_unique | Key_name  | Seq_in_index | Column_name  | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | 
+-------------+------------+-----------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 
| local_deals |   0 | PRIMARY   |   1 | id    | A   |  193893 |  NULL | NULL |  | BTREE  |   |    | 
| local_deals |   0 | unique_deal_ID |   1 | deal_ID   | A   |  193893 |  NULL | NULL |  | BTREE  |   |    | 
| local_deals |   1 | deal_ID   |   1 | deal_ID   | A   |  193893 |  NULL | NULL |  | BTREE  |   |    | 
| local_deals |   1 | store_ID  |   1 | store_ID  | A   |  193893 |  NULL | NULL | YES | BTREE  |   |    | 
| local_deals |   1 | storeOnline_ID |   1 | storeOnline_ID | A   |   3 |  NULL | NULL | YES | BTREE  |   |    | 
| local_deals |   1 | storeChain_ID |   1 | storeChain_ID | A   |   117 |  NULL | NULL | YES | BTREE  |   |    | 
| local_deals |   1 | userProvider_ID |   1 | userProvider_ID | A   |   5 |  NULL | NULL | YES | BTREE  |   |    | 
| local_deals |   1 | expirationDate |   1 | expirationDate | A   |  3127 |  NULL | NULL | YES | BTREE  |   |    | 
| local_deals |   1 | createDate  |   1 | createDate  | A   |  96946 |  NULL | NULL | YES | BTREE  |   |    | 
| local_deals |   1 | city   |   1 | city   | A   |  17626 |  NULL | NULL | YES | BTREE  |   |    | 
| local_deals |   1 | state   |   1 | state   | A   |   138 |  NULL | NULL | YES | BTREE  |   |    | 
| local_deals |   1 | zip    |   1 | zip    | A   |  38778 |  NULL | NULL | YES | BTREE  |   |    | 
| local_deals |   1 | country   |   1 | country   | A   |   39 |  NULL | NULL | YES | BTREE  |   |    | 
| local_deals |   1 | latitude  |   1 | latitude  | A   |  193893 |  NULL | NULL | YES | BTREE  |   |    | 
| local_deals |   1 | longitude  |   1 | longitude  | A   |  193893 |  NULL | NULL | YES | BTREE  |   |    | 
| local_deals |   1 | eventDate  |   1 | eventDate  | A   |  4215 |  NULL | NULL | YES | BTREE  |   |    | 
| local_deals |   1 | isNowDeal  |   1 | isNowDeal  | A   |   3 |  NULL | NULL | YES | BTREE  |   |    | 
| local_deals |   1 | businessType |   1 | businessType | A   |   5 |  NULL | NULL | YES | BTREE  |   |    | 
| local_deals |   1 | dealType  |   1 | dealType  | A   |   5 |  NULL | NULL | YES | BTREE  |   |    | 
| local_deals |   1 | submit_ID  |   1 | submit_ID  | A   |   5 |  NULL | NULL | YES | BTREE  |   |    | 
+-------------+------------+-----------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 

講解運行所揭示延伸:

+------+-------------+-------------+------+----------------------------------+-------+---------+-------+-------+----------+----------------------------------------------------+ 
| id | select_type | table  | type | possible_keys     | key | key_len | ref | rows | filtered | Extra            | 
+------+-------------+-------------+------+----------------------------------+-------+---------+-------+-------+----------+----------------------------------------------------+ 
| 1 | SIMPLE  | local_deals | ref | state,country,latitude,longitude | state | 35  | const | 52472 | 100.00 | Using index condition; Using where; Using filesort | 
+------+-------------+-------------+------+----------------------------------+-------+---------+-------+-------+----------+----------------------------------------------------+ 

有大約在20萬個錶行。奇怪的是,它忽略了經緯度指數,因爲那些應該更多地過濾表格。運行查詢,我去掉了「國家」,「國家」,其中的命令顯示了以下解釋:

+------+-------------+-------------+-------+--------------------+-----------+---------+------+-------+----------+----------------------------------------------------+ 
| id | select_type | table  | type | possible_keys  | key  | key_len | ref | rows | filtered | Extra            | 
+------+-------------+-------------+-------+--------------------+-----------+---------+------+-------+----------+----------------------------------------------------+ 
| 1 | SIMPLE  | local_deals | range | latitude,longitude | longitude | 5  | NULL | 30662 | 100.00 | Using index condition; Using where; Using filesort | 
+------+-------------+-------------+-------+--------------------+-----------+---------+------+-------+----------+----------------------------------------------------+ 

這表明經度指數將更好地表篩選到30662行。我在這裏錯過了什麼嗎?我如何讓MySQL使用所有查詢。請注意,該表是InnoDB,我正在使用MySQL 5.5。

+0

你沒有包括第二個查詢,但我相信你使用單個範圍?檢查MySQL索引[** TIPS **](http://mysql.rjweb.org/doc.php/index_cookbook_mysql) –

+0

索引「所有列」幾乎總是愚蠢的。 –

+0

你真的想'ORDER BY'上的'DESCENDING'嗎? –

回答

2

查詢的最佳索引是(country, state, latitude, longitude)countrystate可以交換)的複合索引。 MySQL在多列索引上有很好的文檔,它是here

基本上,latitudelongitude並不是單獨選擇。不幸的是,標準的B-tree索引只支持一個不等式,而你的查詢有兩個。

其實,如果你想GIS處理,那麼你應該使用一個空間擴展到MySQL。

+0

洛杉磯50英里範圍內可能有30K排。所以,雖然這是一個相當不錯的指數,但它會扼殺不得不處理30K行。或許「邊界框」應該更加緊湊,至少從一開始就是如此。 –

+0

我對一個更緊密的邊界框的擔憂是,在農村地區它將不會返回結果,導致我需要在這些情況下運行多個查詢。是否有一些解決方法,例如首先測試5英里然後10,然後25然後50英里? – user2694306

+0

@ user2694306。 。 。您的查詢不使用距離範圍。根據距離排序並使用「限制」是很好的。爲了提高性能,您可能需要查看空間擴展。 –

0

根據表格的大小,Gordon的建議索引可能「足夠好」。如果您需要獲得更高的性能,則需要使用2D分區技術,其中您在latitude上分區,並安排InnoDB PRIMARY KEYlongitude開頭。更多詳細信息和示例代碼,請參閱my article

0

對於這樣問題的一種通用的技術是建立一個子查詢與這些屬性:

  • 它返回不超過LIMIT行;這些都是你需要的。
  • 有一個涉及列的「覆蓋索引」,加上PRIMARY KEY
  • 您正在使用InnoDB。

喜歡的東西

SELECT b. ..., a.distance 
    FROM local_deals b 
    JOIN (
     SELECT id, 
       (...) AS distance, 
      FROM local_deals 
      WHERE latitude BETWEEN 33.34536 AND 34.79464 
       AND longitude BETWEEN -119.61863 AND -117.18137 
       AND state = 'CA' 
       AND country = 'US' 
      ORDER BY distance ASC 
      LIMIT 48 OFFSET 0 
     ) AS a ON b.id = a.id 
    ORDER BY a.distance; 

INDEX(country, state, latitude, longitude, id) -- `id` is the PK 
-- country and state first (because of '='); id last. 

爲什麼這會有所幫助...

  • 該指數是 「覆蓋」,所以冗長的掃描(很多超過48行)完全完成在指數的BTree。這減少了巨大的表的I/O。
  • 所有其他字段(b。*)不是通過tmp表格等等拖出來的,只有48個字段是處理這些字段的集合。
  • 由於「聚簇PK」,通過id查找48個數據在InnoDB中特別有效。

當與「龐大」表,其中,I/O占主導地位,該技術可以被進行計數工作:

  • 1,或少量的,需要用於子查詢在索引塊。請注意,所需的記錄是連續的,或幾乎如此。 (好吧,如果有30K可以查看,它可能會超過100個塊;因此我的評論是關於縮小邊界框的開始。)
  • 然後48(LIMIT)通過id隨機取得48行。

如果沒有子查詢,需要提取龐大的行。而且,根據所使用的索引,這可能會高達30K塊。這要慢幾個數量級。

此外,48行與30K行將被寫入tmp表進行排序(ORDER BY)。