【问题标题】:Why does MySQL not use optimal indexesMySQL为什么不使用最优索引
【发布时间】:2016-06-24 03:47:06
【问题描述】:

我正在尝试优化我的查询,但是,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 |
+------+-------------+-------------+------+----------------------------------+-------+---------+-------+-------+----------+----------------------------------------------------+

表中有大约 200k 行。奇怪的是它忽略了纬度和经度索引,因为它们应该更多地过滤表格。运行一个查询,我删除“州”和“国家”,其中命令显示以下解释:

+------+-------------+-------------+-------+--------------------+-----------+---------+------+-------+----------+----------------------------------------------------+
| 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 |
+------+-------------+-------------+-------+--------------------+-----------+---------+------+-------+----------+----------------------------------------------------+

这表明经度索引可以更好地将表过滤为 30,662 行。我在这里错过了什么吗?如何让 MySQL 使用所有查询。请注意,该表是 InnoDB,我使用的是 MySQL 5.5。

【问题讨论】:

  • 您没有包含第二个查询,但我相信您使用的是单个范围?检查 MySQL 索引TIPS
  • 索引“所有列”几乎总是愚蠢的。
  • 您真的想要DESCENDING 上的ORDER BY 吗?
  • 52K 和 30K 行 -- (1) 洛杉矶有很多条目,(2) EXPLAIN 有时对这些估计值不是很精确。

标签: mysql sql indexing


【解决方案1】:

解决此类问题的通用技术是使用以下属性构建子查询:

  • 它返回的行数不超过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 中完成。这减少了 huge 表的 I/O。
  • 所有其他字段 (b.*) 都没有通过 tmp 表等进行拖拉。只有 48 个字段集被处理。
  • 由于“集群 PK”,按 id 进行的 48 次查找在 InnoDB 中特别有效。

当使用 I/O 占主导地位的“巨大”表时,可以这样计算这种技术:

  • 子查询需要索引中的 1 个或少量块。请注意,所需的记录是连续的,或者几乎是连续的。 (好吧,如果要查看 30K,它可能超过 100 个块;因此我对缩小边界框的评论开始。)
  • 然后 48 (LIMIT) 通过 id 随机获取 48 行。

没有子查询,需要获取大量行。而且,根据所使用的索引,最多可以提取 30K 块。这要慢几个数量级。

此外,48 行与 30K 行将写入 tmp 表进行排序 (ORDER BY)。

【讨论】:

    【解决方案2】:

    根据您的表格大小,Gordon 建议的索引可能“足够好”。如果您需要获得更高的性能,则需要使用 2D 分区技术,其中您在 latitude 上进行分区,并安排 InnoDB PRIMARY KEYlongitude 开头。更多详细信息和示例代码,请访问my article

    【讨论】:

      【解决方案3】:

      查询的最佳索引是(country, state, latitude, longitude) 上的复合索引(countrystate 可以交换)。 MySQL 有很好的多列索引文档,here

      基本上,latitudelongitude 并不是特别具有选择性。不幸的是,标准的 B 树索引只支持一个不等式,而您的查询有两个。

      其实,如果你想要GIS处理,那么你应该使用MySQL的空间扩展。

      【讨论】:

      • 洛杉矶 50 英里范围内很可能有 30K 行。因此,虽然这是一个相当不错的索引,但它会因必须处理 30K 行而窒息。也许“边界框”应该更紧凑,至少一开始是这样。
      • 我对更严格的边界框的担忧是,在农村地区它不会返回任何结果,导致我需要在这些情况下运行多个查询。是否有一些解决方法,例如先测试 5 英里,然后 10 英里,然后 25 英里,然后 50 英里?
      • @user2694306 。 . .您的查询不使用距离范围。按距离排序并使用limit 很好。出于性能考虑,您可能需要研究空间扩展。
      猜你喜欢
      • 2018-08-11
      • 1970-01-01
      • 2011-11-09
      • 2013-10-29
      • 2014-03-18
      • 1970-01-01
      • 1970-01-01
      • 2021-07-13
      • 2013-09-10
      相关资源
      最近更新 更多