【问题标题】:MySQL query optimization with complex index复杂索引的 MySQL 查询优化
【发布时间】:2018-03-21 13:32:39
【问题描述】:

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

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,因此每一对纬度/经度都是唯一的。这意味着大约 50m 的分辨率可以满足我的需求。

现在的问题是:每次我需要知道给定的纬度/经度 (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 意味着最接近的近似值,所以假设每次我找到一个大于 10 的 R 时,我需要添加一个新行地址表。 地址表目前包含大约 615k 行。

问题是尽管我已经放置了索引,但这个查询太慢了(在 2x Xeon 服务器上大约需要 2 秒)。在 Explain 的结果下方:

【问题讨论】:

  • 我尝试将解释结果的快照图像上传为图片,但是当您需要上传图像时,stackoverflow 的编辑器很可怕...不要问我为什么在上面页面...
  • 您在“计算”列上排序,这从未使用索引进行优化,因为 MySQL 需要对 615088 行的估计使用快速排序排序算法...“使用文件排序”在额外列中关闭解释输出表明..
  • @RaymondNijland,你说得对,这是问题的核心。所以真正的问题是如何检查地址表中可用的纬度/经度中最接近的纬度/经度?我只需要找到最接近的那个,不管我如何检查。是否可以基于纬度和经度对创建“计算索引”?
  • 检查我的答案可能会帮助你
  • 不要费心让IDPK两次。

标签: mysql indexing latitude-longitude query-performance


【解决方案1】:

您不能通过检索附近纬度和经度的固定数据集并计算评级 (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

【讨论】:

  • 您的建议是正确的,即使不是 100% 充分,但您让我找到了解决问题的正确方法,这就是为什么我给了您当之无愧的 +1。问题是我真的不需要距离,只需要知道距离是否超过一定水平。所以我修改了我的回答中描述的查询。
【解决方案2】:

提供一个“边界框”,而不是计算距离(或除此之外)。这会更快。

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

一旦有了UNIQUE KEY IDX_ADDRESS_UNIQUE_LATLNG (LAT, LNG),就不需要KEY IDX_ADDRESS_LAT (LAT)

*10000 可以放入MEDIUMINT。大约 16 米或 52 英尺。

【讨论】:

  • 恭喜!您在mysql.rjweb.org/doc.php/latlng 的链接真的专注于我的问题!实际上,我的解决方案(在您的建议之前开发)朝着实现“边界框”的相同方向发展,但没有分区。非常有用且做得很好的网页!非常感谢!
【解决方案3】:

根据 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)。

【讨论】:

  • 又名“边界框”。需要纬度的余弦来说明经线比纬线更靠近。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-24
  • 2012-01-27
  • 1970-01-01
相关资源
最近更新 更多