RAILS中的POSTGIS查询优化

sov*_*ndy 1 sql postgis ruby-on-rails postgresql-9.1

我正在使用postgis2.0和postgres 9.1 db.我的目标是尽可能接近优化查询,以获得特定半径范围内的附近位置,并将它们与距离顺序放在一起.Location模型具有latlong空间类型postgis扩展的属性和distance_from计算距离给定POINT(长lat)的距离的方法.我在rails代码中写了如下查询:

def self.nearby(lat, long, radius)
    nearby = Location.where("ST_DWithin(ST_GeomFromEWKB(latlong), ST_GeomFromText('POINT(#{long} #{lat})', 4326),?, false )", radius)
    .order("ST_Distance_Sphere(ST_GeomFromEWKB(latlong) , ST_GeomFromText('POINT(#{long} #{lat})', 4326) ) ")
    .map{|ar| 
      { "id" => ar.id,
        "distance" => ar.distance_from(lat, long)
      } 
    }
end
Run Code Online (Sandbox Code Playgroud)

我可以看到我distance用order子句和map子句双重计算两次,但是想不出我应该如何存储sql查询的距离的立即值.所以在map{}我重新计算它.

 `.order("ST_Distance_Sphere(ST_GeomFromEWKB(latlong) , ST_GeomFromText('POINT(#{long} #{lat})', 4326) ) ")`
Run Code Online (Sandbox Code Playgroud)

"distance" => ar.distance_from(lat, long)

如果我没有错,在我的情况下使用ST_DWithin可以帮助我快速得到一个位置是否在内而不是先计算距离.因此,如果说一个查询只返回10-100个位置,ST_DWithin将比纯粹使用STDistance有助于加快查询速度.

我还能提高多少?我的位置数据库大小约为10000条记录.感谢您的时间,谢谢.

Ben*_*n M 6

目前我还在使用Rails和PostGIS的应用程序.:-)

对于复杂查询,我选择了编写纯SQL而不是使用ActiveRecords方法的方法,使事情更容易维护.

你的是:

SELECT
  *
FROM location
WHERE
  ST_DWithin(ST_GeomFromEWKB(latlong),
    ST_GeomFromText('POINT(#{long} #{lat})', 4326), ?, false)
ORDER BY
  ST_Distance_Sphere(ST_GeomFromEWKB(latlong),
    ST_GeomFromText('POINT(#{long} #{lat})', 4326))
Run Code Online (Sandbox Code Playgroud)

顺便说一句,这些坐标是在latlon没有g;-)的情况下调用的

给我几分钟,我会试着弄清楚Postgres如何优化您的查询,以及是否需要手动优化它.


这个查询可以更快(如果有很多匹配),但也可以更慢,因为ST_DWithinST_Distance或快得多ST_Distance_Sphere.所以请用大量数据测试它:

SELECT
  *
FROM ( 
  SELECT
    l.*,
    (
      ST_DISTANCE_SPHERE(ST_GeomFromEWKB(latlong),
        ST_GeomFromText('POINT(#{long} #{lat})', 4326))
    ) AS d
  FROM location l
) x
WHERE d < ?
ORDER BY d
Run Code Online (Sandbox Code Playgroud)

说明:

您的原始查询将首先使用对所有找到的对象的快速ST_DWithin和后续调用ST_Distance_Sphere来过滤结果.

我的查询将计算ST_Distance_Sphere数据库中的所有对象,然后使用整数比较过滤它们.


要在Rails中使用,您可以直接调用 Location.find_by_sql(...)


解释分析

(调用我的表,调用measurement包含Point的列groundtruth)

您的查询:

Sort  (cost=341.05..341.06 rows=1 width=172) (actual time=3.676..3.731 rows=816 loops=1)
  Sort Key: (_st_distance(geography(groundtruth), '0101000020E6100000EE7C3F355EF24F4019390B7BDA011940'::geography, 0::double precision, false))
  Sort Method: quicksort  Memory: 139kB
  ->  Bitmap Heap Scan on measurement m  (cost=9.67..341.04 rows=1 width=172) (actual time=0.330..3.257 rows=816 loops=1)
        Recheck Cond: (groundtruth && '01030000000100000005000000EE7C3F355E724D4064E42CEC6907F43FEE7C3F355E724D408C9C853DED80264077BE9F1A2F3951408C9C853DED80264077BE9F1A2F39514064E42CEC6907F43FEE7C3F355E724D4064E42CEC6907F43F'::geometry)
        Filter: (('0101000000EE7C3F355EF24F4019390B7BDA011940'::geometry && st_expand(groundtruth, 5::double precision)) AND _st_dwithin(groundtruth, '0101000000EE7C3F355EF24F4019390B7BDA011940'::geometry, 5::double precision))
        ->  Bitmap Index Scan on groundtruth_idx  (cost=0.00..9.67 rows=189 width=0) (actual time=0.186..0.186 rows=855 loops=1)
              Index Cond: (groundtruth && '01030000000100000005000000EE7C3F355E724D4064E42CEC6907F43FEE7C3F355E724D408C9C853DED80264077BE9F1A2F3951408C9C853DED80264077BE9F1A2F39514064E42CEC6907F43FEE7C3F355E724D4064E42CEC6907F43F'::geometry)
Total runtime: 3.932 ms
Run Code Online (Sandbox Code Playgroud)

我的查询:

Sort  (cost=9372.84..9391.92 rows=7634 width=172) (actual time=19.256..19.312 rows=816 loops=1)
  Sort Key: (st_distance(m.groundtruth, '0101000000EE7C3F355EF24F4019390B7BDA011940'::geometry))
  Sort Method: quicksort  Memory: 139kB
  ->  Seq Scan on measurement m  (cost=0.00..8226.01 rows=7634 width=172) (actual time=0.040..18.863 rows=816 loops=1)
        Filter: (st_distance(groundtruth, '0101000000EE7C3F355EF24F4019390B7BDA011940'::geometry) < 5::double precision)
Total runtime: 19.396 ms
Run Code Online (Sandbox Code Playgroud)

正如您所看到的:22901中只有816个匹配的行.我的查询耗时更长.

如果我使距离更大,则两个查询都会快速相等.

如果所有行(= 22901行)都在搜索范围内,我的查询速度会快一点:180对210ms.

所以你可能会继续使用你的解决方案;)


另一个可能获得1-2%性能的建议:不要使用GeomFromText,你可以rgeo直接给你的数据库一个Point对象作为参数,而不是2个坐标.