如何为PostGIS距离查询正确设置索引?


18

我正在构建一个应用程序,该应用程序应查询并返回距离数公里Record的表中的每个表。和的位置取决于Google Geocode API提供的信息。XPointXRecordsPointX(long/lat)

我是PostGIS的新手。经过快速研究,我发现了这个问题。答案似乎是:

SELECT *
FROM your_table
WHERE ST_Distance_Sphere(the_geom, ST_MakePoint(your_lon,your_lat)) <= radius_mi * 1609.34

问题是:即使我只是开始使用GIS,但是当我看上述查询时,我无法想象它如何使用索引。有2个函数调用。我想象每张桌子都要扫描Record。我想错了:)

问题:PostGIS是否具有任何能够使上述查询具有性能的索引类型?如果没有,那么推荐的方法将满足我的需求?


确保您建立正确的索引,在铸造,下至地理,并应用ST_SetSRID()ST_MakePoint铸造地理查询之前。
文斯

Answers:


38

geometry使用带有WGS 1984地理数据(SRID 4326)的带有列的大表可获得良好的大地测量查询性能的关键有两个:

  1. 使用ST_DWithin功能,该功能使用可用的空间索引进行搜索,并找到具有笛卡尔距离的地理特征
  2. 在地理投射上建立额外的索引,因此ST_DWithin可以使用它

因此,让我们看看现实世界中会发生什么。首先,我们需要创建并填充一百万个随机点的表:

DROP TABLE IF EXISTS example1
;

CREATE TABLE example1 (
    idcol   serial      NOT NULL,
    geomcol geometry        NULL,
    CONSTRAINT  example1_pk PRIMARY KEY (idcol),
    CONSTRAINT  enforce_srid CHECK (st_srid(geomcol) = 4326)
)
with (
    OIDS=FALSE
);

INSERT INTO example1(geomcol)
SELECT  ST_SetSRID(
            ST_MakePoint(
            (random()*360.0) - 180.0,
            (acos(1.0 - 2.0 * random()) * 2.0 - pi()) * 90.0 / pi()),
            4326) as geomcol
FROM  generate_series(1, 1000000) vtab;

CREATE INDEX example1_spx ON example1 USING GIST (geomcol);
-- (took about 22 sec)

如果执行ST_Distance查询,我们将获得预期的全表扫描:

EXPLAIN ANALYZE VERBOSE
SELECT  count(*)
FROM    example1
WHERE   ST_Distance(geomcol::geography,ST_SetSRID(ST_MakePoint(6.9333,46.8167),4326)::geography) < 30 * 1609.34
;

Aggregate  (cost=274167.33..274167.34 rows=1 width=0) (actual time=4940.531..4940.532 rows=1 loops=1)
  Output: count(*)
  ->  Seq Scan on bob.example1  (cost=0.00..273334.00 rows=333333 width=0) (actual time=592.766..4940.509 rows=11 loops=1)
        Output: idcol, geomcol
        Filter: (_st_distance((example1.geomcol)::geography, '0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography, 0::double precision, true) < 48280.2::double precision)
        Rows Removed by Filter: 999989
Planning time: 2.137 ms
Execution time: 4940.568 ms

现在,如果使用ST_DWithin,我们仍然可以进行全表扫描(尽管扫描速度更快):

EXPLAIN ANALYZE VERBOSE
SELECT  count(*)
FROM    example1
WHERE   ST_DWithin(geomcol::geography,ST_SetSRID(ST_MakePoint(6.9333,46.8167),4326)::geography,30 * 1609.34)
;

Aggregate  (cost=405867.33..405867.34 rows=1 width=0) (actual time=908.716..908.716 rows=1 loops=1)
  Output: count(*)
  ->  Seq Scan on bob.example1  (cost=0.00..405834.00 rows=13333 width=0) (actual time=38.449..908.700 rows=7 loops=1)
        Output: idcol, geomcol
        Filter: (((example1.geomcol)::geography && '0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography) AND ('0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography && _st_expand((example1.geomcol)::geography, 48280.2::double precision) (...)
        Rows Removed by Filter: 999993
Planning time: 2.017 ms
Execution time: 908.763 ms

这是最后一部分-建立覆盖率索引(广播地理):

CREATE INDEX example1_gpx ON example1 USING GIST (geography(geomcol));
-- (Takes an extra 13 sec)

EXPLAIN ANALYZE VERBOSE
SELECT  count(*)
FROM    example1
WHERE   ST_DWithin(geomcol::geography,ST_SetSRID(ST_MakePoint(6.9333,46.8167),4326)::geography,30 * 1609.34)
;

Aggregate  (cost=96538.95..96538.96 rows=1 width=0) (actual time=0.775..0.775 rows=1 loops=1)
  Output: count(*)
  ->  Bitmap Heap Scan on bob.example1  (cost=8671.62..96505.62 rows=13333 width=0) (actual time=0.586..0.769 rows=19 loops=1)
        Output: idcol, geomcol
        Recheck Cond: ((example1.geomcol)::geography && '0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography)
        Filter: (('0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography && _st_expand((example1.geomcol)::geography, 48280.2::double precision)) AND _st_dwithin((example1.geomcol)::geography, '0101000020E61000005D6DC5FEB2BB1B40545227A089684740':: (...)
        Rows Removed by Filter: 14
        Heap Blocks: exact=33
        ->  Bitmap Index Scan on example1_gpx  (cost=0.00..8668.29 rows=200000 width=0) (actual time=0.384..0.384 rows=33 loops=1)
              Index Cond: ((example1.geomcol)::geography && '0101000020E61000005D6DC5FEB2BB1B40545227A089684740'::geography)
Planning time: 2.572 ms
Execution time: 0.820 ms

最后,优化器正在使用空间索引,它显示了,但是朋友之间的三个数量级是什么?

一些警告:

  • 我是一个数据库书呆子,所以我的家用PC拥有16Gb RAM,六个3.3Ghz内核和一个用于数据库默认表空间的256Gb SSD。你的旅费可能会改变

  • 我在每次查询前都重新运行了创建SQL,以相对于缓存中的“热门”页面来平整竞争环境,但这可能会产生略有不同的结果,因为未将相同的随机种子用于不同的运行

并注意:

  • 我调整了原始的{-90,+ 90}纬度范围,以使用反余弦进行等面积分布(偏向极点的偏向较小)

1
这是我在Stackexchange社区获得的最佳答案之一。我仍然没有尝试过,但是您提供了一个完整的示例,我可以完全理解。非常感谢@Vince。
andrerpena

1
有什么理由不将地理信息存储为地理信息?ST_Distance和ST_DWithin均属于预期地理位置。而且,如果这样做的话,我们就不需要地理上额外的索引转换几何。
andrerpena

这是一个不同的问题,如果被问到,可能会基于意见而封闭。
文斯

1
在Google中遇到此结果,并感谢@Vince的回答。的有力铸造GEOM点到geograhpy最小差异拉着我的查询时间从平均43秒而不是10毫秒..
愤怒的84

很棒的帖子,但是我认为`(acos(1.0-2 * random())* 180.0)/ pi())不正确。范围不是从-90到90
hxd1011 '19
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.