【问题标题】:Is mysql using my index or not, and can the performance of geokit be improved?mysql是否使用我的索引,geokit的性能可以提高吗?
【发布时间】:2009-08-26 11:13:44
【问题描述】:

我在 rails 应用程序中使用 geokit (acts_as_mappable),当有大量模型时,径向或边界搜索的性能会大大降低(我尝试过 1-200 万个,但问题无疑会出现比这更早)。

Geokit 根据表中的 lat 和 lng 列(纬度和经度)进行所有计算。为了提高性能,geokit 通常会添加一个边界框“where”子句,目的是使用纬度和经度的组合索引来提高性能。然而,对于大量模型,它仍然非常慢,在我看来,边界框子句应该比它有更多的帮助。

所以我的问题是,有没有办法让 mysql 更好地利用组合的 lat/lng 索引或以其他方式提高 geokit sql 查询的性能?或者,可以使 lat/lng 的组合索引更有帮助吗?

编辑:我现在可以使用 Rails 并更详细地编写解决方案 here

更多背景

例如,此查询查找给定点 10 英里范围内的所有地点。 (我添加 .length 只是为了确定返回多少结果 - 在 geokit 中有更好的表达方式,但我想强制执行更典型的 SQL 查询)。

Place.find(:all,:origin=>latlng,:within=>10).length

在 mac mini 上大约需要 14 秒。这是解释计划

mysql> explain SELECT *, (ACOS(least(1,COS(0.898529183781244)*COS(-0.0157233221653665)*COS(RADIANS(places.lat))*COS(RADIANS(places.lng))+    ->  COS(0.898529183781244)*SIN(-0.0157233221653665)*COS(RADIANS(places.lat))*SIN(RADIANS(places.lng))+    ->  SIN(0.898529183781244)*SIN(RADIANS(places.lat))))*3963.19)
    ->  AS distance FROM `places` WHERE (((places.lat>51.3373601471464 AND places.lat<51.6264998528536 AND places.lng>-1.13302245886176 AND places.lng<-0.668737541138245)) AND ( (ACOS(least(1,COS(0.898529183781244)*COS(-0.0157233221653665)*COS(RADIANS(places.lat))*COS(RADIANS(places.lng))+
    ->  COS(0.898529183781244)*SIN(-0.0157233221653665)*COS(RADIANS(places.lat))*SIN(RADIANS(places.lng))+
    ->  SIN(0.898529183781244)*SIN(RADIANS(places.lat))))*3963.19)
    ->  <= 10)) 
    -> ;
+----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+
| id | select_type | table  | type  | possible_keys               | key                         | key_len | ref  | rows  | filtered | Extra       |
+----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+
|  1 | SIMPLE      | places | range | index_places_on_lat_and_lng | index_places_on_lat_and_lng | 10      | NULL | 87554 |   100.00 | Using where | 
+----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+

所以 mysql 正在检查 87554 行,即使结果中的位置数是 1135(边界框中的实际位置数只有 1323)。

这些是索引上的统计信息(使用 rails 迁移 add_index :places, [:lat, :lng]):

| Table  | Non_unique | Key_name                         | Seq_in_index | Column_name      | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
| places |          1 | index_places_on_lat_and_lng      |            2 | lng              | A         |     1373712 |     NULL | NULL   | YES  | BTREE      |         |

它似乎也与三角计算无关,因为对边界框执行类似的查询会导致查询更简单,但它的性能同样很差:

Place.find(:all,:bounds=>GeoKit::Bounds.from_point_and_radius(latlng,10)).length

给出一个类似的解释计划:

   mysql> explain SELECT * FROM `places` WHERE ((places.lat>51.3373601471464 AND places.lat<51.6264998528536 AND places.lng>-1.13302245886176 AND places.lng<-0.668737541138245)) ;
    +----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+
    | id | select_type | table  | type  | possible_keys               | key                         | key_len | ref  | rows  | filtered | Extra       |
    +----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+
    |  1 | SIMPLE      | places | range | index_places_on_lat_and_lng | index_places_on_lat_and_lng | 10      | NULL | 87554 |   100.00 | Using where | 
    +----+-------------+--------+-------+-----------------------------+-----------------------------+---------+------+-------+----------+-------------+

【问题讨论】:

    标签: mysql performance sql-execution-plan geokit


    【解决方案1】:

    普通的B-Tree 索引对于这样的查询不太好。

    对于您的查询,range 访问方法用于以下条件:

    places.lat > 51.3373601471464 AND places.lat < 51.6264998528536
    

    ,这甚至没有考虑lon

    如果你想使用空间能力,你应该将你的位置保留为Points,创建它们的SPATIAL索引并使用MBRContains过滤边界框:

    ALTER TABLE places ADD place_point GEOMETRY
    
    CREATE SPATIAL INDEX sx_places_points ON places (place_point)
    
    UPDATE  places
    SET     place_point = Point(lat, lon)
    
    SELECT  *
    FROM    places
    WHERE   MBRContains(LineString(Point(51.3373, -1.1330), Point(51.6264, -0.6687)), place_point)
            AND -- do the fine filtering here
    

    更新:

    CREATE TABLE t_spatial (id INT NOT NULL, lat FLOAT NOT NULL, lon FLOAT NOT NULL, coord GEOMETRY) ENGINE=MyISAM;
    
    INSERT
    INTO    t_spatial (id, lat, lon)
    VALUES  (1, 52.2532, 20.9778);
    
    UPDATE  t_spatial
    SET     coord = Point(lat, lon);
    

    这在5.1.35 中对我有用。

    【讨论】:

    • 这很有趣——在这种情况下应该有什么样的索引?
    • 谢谢 - 这听起来会更好,我会尝试一下。还有一种方法可以在不使用空间的情况下改进当前查询(因为 geokit 目前不使用 mysql 空间的东西)?
    • 有趣的是,如果我运行这个查询 SELECT * FROM places WHERE ((places.lat>51.3373601471464 AND places.lat
    • @frankodwyer:如果不重写查询,就不能做太多事情。如果MySQL 返回42078 行,那只是意味着您的大多数位置都位于51.337351.6264 之间(覆盖整个伦敦市),所以你很难责怪MySQL,它只是返回回来放什么:)
    • @frankodwyer:这确实是一个普遍问题(两列范围过滤)。 B-Tree 索引无法通过设计处理它。这是一个众所周知的问题:如何查找 IP 地址所属的所有网络,如何查找包含某个时间点的所有日期范围等。这正是 R-Tree (SPATIAL) 索引的用途。有关类似问题,请参阅我博客中的这些文章:explainextended.com/2009/04/04/banning-ipsexplainextended.com/2009/07/01/overlapping-ranges-mysql
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-17
    • 1970-01-01
    相关资源
    最近更新 更多