具有复杂索引的MySQL查询优化

时间:2018-03-21 13:32:39

标签: mysql indexing latitude-longitude query-performance

我有一个用于简单反向地理编码的数据库。数据库依赖于包含纬度,经度和地名的表格。每当经纬度不存在时,或者更好,每当搜索到的纬度,经度与现有纬度,经度相差太大时,我使用GoogleMaps反向地理编码服务添加新行。 在代码下面生成地址表:

CREATE TABLE `data_addresses` (
    `ID` int(11) NOT NULL COMMENT 'Primary Key',
    `LAT` int(11) NOT NULL COMMENT 'Latitude x 10000',
    `LNG` int(11) NOT NULL COMMENT 'Longitude x 10000',
    `ADDRESS` varchar(128) NOT NULL COMMENT 'Reverse Geocoded Street Address'
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 
ALTER TABLE `data_addresses`
    ADD PRIMARY KEY (`ID`),
    ADD UNIQUE KEY `IDX_ADDRESS_UNIQUE_LATLNG` (`LAT`,`LNG`),
    ADD KEY `IDX_ADDRESS_LAT` (`LAT`),
    ADD KEY `IDX_ADDRESS_LNG` (`LNG`);
ALTER TABLE `data_addresses`
    MODIFY `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT 'Primary Key';

正如您所看到的,诀窍是在纬度和经度上使用两个索引。由于通常纬度和经度是浮点数,我们使用它们的值乘以10000,因此每对夫妇的纬度/经度都是唯一的。这意味着大约50米的分辨率满足了我的需求。

现在问题是:每当我需要知道给定的纬度/经度(MyLat,MyLon)是否已经存在时,我执行以下查询:

SELECT `id`, ROUND(SQRT(POW(ABS(`LAT`-ROUND(MyLat*10000)),2)+POW(ABS(`LNG`-ROUND(MyLon*10000)),2))) AS R FROM splc_smarttrk.`data_addresses` ORDER BY R ASC LIMIT 1

此查询将返回给我最近的点并且还会给我R(评级):较小的R表示最接近的近似值,所以说每当我发现R大于10时我需要添加一个新行地址表。 目前地址表包含大约615k行。

问题在于,尽管我放置了索引,但此查询太慢(在2x Xeon服务器上大约需要2秒)。在解释结果下面:

enter image description here

3 个答案:

答案 0 :(得分:2)

您无法通过查找附近纬度和经度的固定数据集并计算评级(R)并在此固定数据集上选择最小评级来优化此项。

未测试的p.s可能包含排序错误。但它可能会帮助你。

SELECT 
   id 
 , ROUND(SQRT(POW(ABS(`LAT`-ROUND([LAT]*10000)),2)+POW(ABS(`LNG`- ROUND([LNG]*10000)),2))) AS R

FROM ( 

  SELECT 
   LAT 
  FROM  
   data_addresses
  WHERE 
   LAT <= [LAT]  
  ORDER BY
   LAT DESC
  LIMIT 100

  UNION ALL

  SELECT 
   LAT   
  FROM 
   data_addresses
  WHERE 
   LAT >= [LAT]
  ORDER BY
   LAT ASC
  LIMIT 100

  SELECT 
   LNG 
  FROM 
   data_addresses
  WHERE 
   LNG <= [LNG]
  ORDER BY
   LNG DESC
  LIMIT 100

  UNION ALL

  SELECT 
   LNG
  FROM 
   data_addresses
  WHERE 
   LNG >= [LNG]
  ORDER BY
   LNG ASC
  LIMIT 100
) 
 AS data_addresses_range
ORDER BY 
 R ASC
LIMIT 1

答案 1 :(得分:1)

不是计算距离(或除了),而是提供一个&#34;边界框&#34;。这会快得多。

这里复杂的代码还是更快:mysql.rjweb.org/doc.php/latlng

获得UNIQUE KEY IDX_ADDRESS_UNIQUE_LATLNG (LAT, LNG)后,无需KEY IDX_ADDRESS_LAT (LAT)

* 10000可以放在MEDIUMINT中。它大约16米或52英尺。

答案 2 :(得分:0)

根据Raymond Nijland的建议,我将查询修改如下:

SELECT  `id` AS ID,
ROUND(SQRT(POW(ABS(`LAT`-ROUND(NLat*10000)), 2) +
           POW(ABS(`LNG`-ROUND(NLon*10000)), 2))
     ) AS RT INTO  ADDR_ID, RATING
    FROM  splc_smarttrk.`data_addresses`
    WHERE  (`LAT` BETWEEN (ROUND(NLat*10000)-R) AND (ROUND(NLat*10000)+R))
      AND  (`LNG` BETWEEN (ROUND(NLon*10000)-R) AND (ROUND(NLon*10000)+R))
    ORDER BY  RT ASC
    LIMIT  1;

这个技巧在最坏的情况下将数据集减少到10条记录,因此尽管有ORDER BY子句,速度仍然很好。事实上,我并不需要知道距离现有点的距离,我只需要知道该距离是否高于给定限制(此处如果在10x10矩形内,这意味着R = 5)。