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

时间:2013-06-26 19:41:49

标签: mysql latitude-longitude haversine

我已将表缩短为仅显示此查询的相关列。它需要两个表,查询需要很长时间,我们甚至没有进入400多万个查询和一个3000多万条记录的日志文件或一个包含100多万条记录的用户表。它让我重新考虑这个......我需要一些指导和建议:

这是表格:

// 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的条目?

1 个答案:

答案 0 :(得分:2)

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

您可以使用lat索引从hasrsine计算中排除整组点对。利用这一事实:每纬度约有69个法定里程,60海里或111.045千米。 (这不完全正确,但它非常接近)。

因此,您可以为查询添加几个条件。这些将在你的纬度指数上添加一个范围扫描,这比你的HAVING条件更快批次

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

这将排除所有距离太远的北方或太远的点,以包含在您的半身距离计算中。这将节省大量时间。

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

最后,float是lat和lon非常好的数据类型。除非您是土木工程师,否则您不需要高精度十进制数据,除非您关心地球的真实形状是大地水准面,而不是球形。如果您关心这一点,您最好使用比半胱氨酸更精确的距离公式。但我们在谈论这里的厘米差异 - 停车场的大水坑,但对于商店发现者来说没有问题。