我有一个mysql(5.0.22)myisam表,里面有大约300k的记录,我想在5英里范围内进行纬度/离子距离搜索。
我有一个覆盖lat / lon字段的索引,当我选择lat / lon时,它是快速的(毫秒响应)。但是当我选择表格中的其他字段时,可能会慢慢减慢到5-8秒。
我正在使用myisam来利用全文搜索。其他索引表现良好(例如,从列表中选择*,其中slug ='xxxxx')。
如何优化查询,表格或索引以加快速度?
我的架构是:
CREATE TABLE `Listing` (
`id` int(10) unsigned NOT NULL auto_increment,
`name` varchar(125) collate utf8_unicode_ci default NULL,
`phone` varchar(18) collate utf8_unicode_ci default NULL,
`fax` varchar(18) collate utf8_unicode_ci default NULL,
`email` varchar(55) collate utf8_unicode_ci default NULL,
`photourl` varchar(55) collate utf8_unicode_ci default NULL,
`thumburl` varchar(5) collate utf8_unicode_ci default NULL,
`website` varchar(85) collate utf8_unicode_ci default NULL,
`categoryid` int(10) unsigned default NULL,
`addressid` int(10) unsigned default NULL,
`deleted` tinyint(1) default NULL,
`status` int(10) unsigned default '2',
`parentid` int(10) unsigned default NULL,
`organizationid` int(10) unsigned default NULL,
`listinginfoid` int(10) unsigned default NULL,
`createuserid` int(10) unsigned default NULL,
`createdate` datetime default NULL,
`lasteditdate` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
`lastedituserid` int(10) unsigned default NULL,
`slug` varchar(155) collate utf8_unicode_ci default NULL,
`aclid` int(10) unsigned default NULL,
`alt_address` varchar(80) collate utf8_unicode_ci default NULL,
`alt_website` varchar(80) collate utf8_unicode_ci default NULL,
`lat` decimal(10,7) default NULL,
`lon` decimal(10,7) default NULL,
`city` varchar(80) collate utf8_unicode_ci default NULL,
`state` varchar(10) collate utf8_unicode_ci default NULL,
PRIMARY KEY (`id`),
KEY `idx_fetch` USING BTREE (`slug`,`deleted`),
KEY `idx_loc` (`state`,`city`),
KEY `idx_org` (`organizationid`,`status`,`deleted`),
KEY `idx_geo_latlon` USING BTREE (`status`,`lat`,`lon`),
FULLTEXT KEY `idx_name` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=DYNAMIC;
我的查询是:
SELECT Listing.name, Listing.categoryid, Listing.lat, Listing.lon
, 3956 * 2 * ASIN(SQRT( POWER(SIN((Listing.lat - 37.369195) * pi()/180 / 2), 2) + COS(Listing.lat * pi()/180) * COS(37.369195 * pi()/180) * POWER(SIN((Listing.lon --122.036849) * pi()/180 / 2), 2) )) rawgeosearchdistance
FROM Listing
WHERE
Listing.status = '2'
AND ( Listing.lon between -122.10913433498 and -121.96456366502 )
AND ( Listing.lat between 37.296909665016 and 37.441480334984)
HAVING rawgeosearchdistance < 5
ORDER BY rawgeosearchdistance ASC;
解释没有geosearch的计划:
+----+-------------+------------+-------+-----------------+-----------------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len |ref | rows | Extra | +----+-------------+------------+-------+-----------------+-----------------+---------+------+------+-------------+ | 1 | SIMPLE | Listing | range | idx_geo_latlon | idx_geo_latlon | 19 | NULL | 453 | Using where | +----+-------------+------------+-------+-----------------+-----------------+---------+------+------+-------------+
用geosearch解释计划:
+----+-------------+------------+-------+-----------------+-----------------+---------+------+------+-----------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+-------+-----------------+-----------------+---------+------+------+-----------------------------+ | 1 | SIMPLE | Listing | range | idx_geo_latlon | idx_geo_latlon | 19 | NULL | 453 | Using where; Using filesort | +----+-------------+------------+-------+-----------------+-----------------+---------+------+------+-----------------------------+
这是覆盖索引的解释计划。以正确的顺序排列列有很大的不同:
+----+-------------+--------+-------+---------------+---------------+---------+------+--------+------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+-------+---------------+---------------+---------+------+--------+------------------------------------------+ | 1 | SIMPLE | Listing | range | idx_geo_cover | idx_geo_cover | 12 | NULL | 453 | Using where; Using index; Using filesort | +----+-------------+--------+-------+---------------+---------------+---------+------+--------+------------------------------------------+
谢谢!
答案 0 :(得分:4)
我认为你真的应该考虑使用 PostgreSQL (结合Postgis)。
我已经放弃了MySQL的地理空间数据(目前),原因如下:
示例:确定“小”选择的多边形与具有超过500万(!)个非常复杂多边形的表之间的重叠多边形,计算这些结果之间的重叠量+排序。平均运行时间:30到100毫秒(这个特定的机器有很多RAM当然。不要忘记调整你的PostgreSQL安装...(阅读文档)。
答案 1 :(得分:1)
您可能只在lat / lon查询中使用'覆盖索引'。当查询使用的索引包含您要选择的数据时,会出现覆盖索引。 MySQL只需要访问索引而不需要访问数据行。 See this for more info。这可以解释为什么lat / lon查询如此之快。
我怀疑计算和返回的行数,减慢了查询的时间。 (加上必须为having子句创建的任何临时表)。
答案 2 :(得分:0)
你真的应该避免在你的select语句中做那么多的数学运算。这可能是你减速很多的根源。请记住,SQL是一种查询语言;它实际上并没有针对三角函数进行优化。
如果您进行非常天真的距离搜索(将返回更多结果),然后将结果取消,SQL会更快,您的整体结果会更快。
如果您想在查询中使用距离,至少应使用平方距离计算; sqrt计算非常慢。平方距离更容易使用。平方距离计算只是使用距离的平方而不是距离;它简单得多。对于笛卡尔坐标系,由于直角三角形的短边的平方和等于斜边的平方,因此计算平方距离(只是两个平方的总和)比计算距离更容易;所有你需要做的就是确保你想要比较的距离(所以不是找到精确的距离并将其与你想要的距离进行比较(比方说5),你找到了方形距离,然后比较到所需距离的平方(25,如果你想要的距离是5)。
答案 3 :(得分:0)
根据您的商家信息的数量,您可以创建包含
的视图listing1Id,Listing2ID,Distance
基本上只是让所有距离“预先计算”
然后你可以这样做:
从v_Distance d中选择listing2ID 距离&lt; 5和listing1ID = XXX
答案 4 :(得分:0)
当我实现地理半径搜索时,我只是将所有美国Zipcodes加载到内存中,使用lat long,然后使用我的半径起点获取半径中的zipcodes列表,然后将其用于我的db查询。当然我使用solr进行搜索,因为搜索空间在2000万行范围内,但应该适用相同的原则。当我在手机上时,对这种反应的浅薄表示道歉。