mysql 27 秒查询(haversine vs geomfromtext)...必须是更好的方法

Posted

技术标签:

【中文标题】mysql 27 秒查询(haversine vs geomfromtext)...必须是更好的方法【英文标题】:mysql 27 second query (haversine vs geomfromtext)... must be a better way 【发布时间】:2013-06-26 19:41:49 【问题描述】:

我已缩短表格以仅显示此查询的相关列。它需要两个表,查询需要很长时间,我们甚至还没有滚动到 4+ 百万个查询和一个包含 30+ 百万条记录的日志文件或具有 1+ 百万条记录的用户表。这让我重新思考……我需要一些指导和建议:

这是桌子:

// an abreviated users table
CREATE TABLE IF NOT EXISTS `users` (
  `userid` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `type` tinyint(1) NOT NULL COMMENT '1=biz, 2=apt, 3=condo, 4=home',
  `distance` decimal(12,7) NOT NULL DEFAULT '1.0000000' COMMENT 'distance away to recv stuff',
  `lat` decimal(12,7) NOT NULL,
  `lon` decimal(12,7) NOT NULL,
  `location` point NOT NULL COMMENT 'GeomFromText',
  UNIQUE KEY `userid` (`userid`),
  KEY `distance` (`distance`),
  KEY `lat` (`lat`),
  KEY `lon` (`lon`),
  SPATIAL KEY `location` (`location`),
  KEY `idx_user_type` (`type`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=501 ;

这是日志表。

// pretty much the full log table
CREATE TABLE IF NOT EXISTS `some_log` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'record num',
  `userid` int(11) unsigned NOT NULL COMMENT 'user id receiving alert',
  `trackid` bigint(20) unsigned NOT NULL COMMENT 'id of msg from message table',
  `sent` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'when msg created',
  PRIMARY KEY (`id`),
  KEY `idx_msg_log_userid` (`userid`),
  KEY `idx_msg_log_trackid` (`trackid`)
) ENGINE=MyISAM  DEFAULT CHARSET=ascii COMMENT='log of all of some stuff' AUTO_INCREMENT=62232;

日志文件的一些示例数据

INSERT INTO `some_log` (`id`, `userid`, `trackid`, `sent`) VALUES
(1, 1, 4, '2011-07-14 18:14:25'),
(2, 2, 4, '2011-07-14 18:14:25'),
(3, 13, 6, '2011-07-25 23:05:54'),
(4, 44, 7, '2011-08-09 16:20:02'),
(5, 12, 17, '2011-08-16 07:35:01'),
(6, 43, 17, '2011-08-16 07:35:01'),
(7, 45, 17, '2011-08-16 07:35:01'),
(8, 12, 18, '2011-08-16 08:05:01'),
(9, 43, 18, '2011-08-16 08:05:01'),
(10, 45, 18, '2011-08-16 08:05:01');

这是查询。

// the query = $distance can be from 1/10th mile to 5 miles
SELECT *,(((acos(sin(($lat *pi()/180)) * sin((`lat`*pi()/180))+cos(($lat *pi()/180)) * cos((`lat`*pi()/180))* cos((($lon - `lon`)*pi()/180))))*180/pi())*60*1.1515) AS dist_x 
   FROM `users`
   WHERE userid NOT IN (
      SELECT userid
      FROM some_log AS L
      WHERE L.trackid='$trackid')
   HAVING dist_x<='$distance' AND dist_x<=`distance` 
   ORDER BY dist_x ASC";

这是另一个查询。这个很慢。

// the above query is pretty quick given the test data
// this query is dog crap slow...
// we added in type and 4 is the most common type of user
SELECT *,(((acos(sin(($lat *pi()/180)) * sin((`lat`*pi()/180))+cos(($lat *pi()/180)) * cos((`lat`*pi()/180))* cos((($lon - `lon`)*pi()/180))))*180/pi())*60*1.1515) AS dist_x 
   FROM `users`
   WHERE type='4' AND userid NOT IN (
      SELECT userid
      FROM some_log AS L
      WHERE L.trackid='$trackid')
   HAVING dist_x<='$distance' AND dist_x<=`distance` 
   ORDER BY dist_x ASC";

一个问题是:是否存在使用 GeomFromText/POINT 字段与纬度/经度搜索的半径/圆搜索?

另一个问题:有没有更好的方法来检查 some_log 表中这个 $userid 已经有 $trackid 的条目?

【问题讨论】:

您能否提供 EXPLAIN 查询的结果?这可能有助于弄清楚为什么 WHERE type='4' 会破坏性能。 您确定type 是一个字符串列吗?如果是整数列,请改用type = 4 【参考方案1】:

忘记表中的空间索引和空间列。它们不会帮助您进行 lat-lon 计算。

您可以使用 lat 索引从半正弦计算中排除整组点对。利用这一事实:每个纬度大约有 69 英里、60 海里或 111.045 公里。 (这不准确,但非常接近)。

因此,您可以在查询中添加几个条件。这些将在您的 lat 索引上添加范围扫描,这比您的 HAVING 条件快 很多

WHERE ....
  AND $lat >= lat - ($distance/69.0)
  AND $lat <= lat + ($distance/69.0)
  ...

这将排除所有太北或太南而无法包含在您的正弦距离计算中的点。这将节省大量时间。

您也可以对 lon 执行此操作,但经度和距离之间的关系因纬度而异。距离两极越近,经度线越靠近。因此公式比较复杂。

最后,float 是一个非常适合经度和纬度的数据类型。此应用程序不需要高精度十进制数据,除非您是土木工程师并且您关心地球的真实形状是大地水准面,而不是球体。如果你关心这一点,你最好使用比haversine更精确的距离公式。但我们在这里讨论的是厘米的差异——停车场里的大水坑,但对于寻找商店的人来说没问题。

【讨论】:

……如果您关心大地水准面,您可能会认为半正弦是不可接受的简化。 :) Ollie,很好的答案...我们刚刚进行了服务器迁移,该过程导致了各种问题(旧配置上的工作在新配置上爆炸)。我们需要的精度是构建级别,所以将小数更改为浮点数会在精度上花费我们任何东西,而不是在查询速度上获得任何东西吗?另外,我阅读了您对用 $lat >= 和 $lat float 适用于建筑级分辨率。这也是您可以从 gps 分辨率中获得的全部信息。由于 mysql 需要将您的十进制数据转换为 double 才能使用数学函数,因此您的查询速度会有所提高。 这是关于纬度/经度精度的事情。 float 非常精确。 double 对数据来说太多了。对我来说,声称我所在城镇的纬度是42.79842367778193 具有欺骗性。就距离计算而言,42.7984 足够接近,42.79841234567842.798400000000 之间的区别实际上(对于 GPS)在噪声中。这就是为什么我提倡使用float 而不是double。您不希望无数客户或老板就这种不精确性对您大喊大叫,所以不要存储它。 是的,无论是片麻岩还是混凝土,GPS 在深峡谷中都不准确。小心!

以上是关于mysql 27 秒查询(haversine vs geomfromtext)...必须是更好的方法的主要内容,如果未能解决你的问题,请参考以下文章

MySQL 大圆距离(Haversine 公式)

MySQL存储函数中的Haversine

Haversine 查询到 Oracle

帮助优化 MySQL 查询

使用 NodeJs 的 Mongodb 地理空间查询(Haversine 公式)

使用 NodeJs 的 Mongodb 地理空间查询(Haversine 公式)