【问题标题】:ST_DWithin does not use index with non-literal argumentST_DWithin 不使用带有非文字参数的索引
【发布时间】:2017-04-16 14:32:44
【问题描述】:

我在 Amazon RDS 上使用带有 PostGIS 2.1.8 的 PostreSQL 9.3。我有一个名为 project_location 的表,它定义了“地理围栏”(每个围栏本质上都是一个坐标和半径)。地理围栏使用名为“位置”的几何列和名为“半径”的双列存储。我在位置列上有一个空间索引。

CREATE TABLE project_location
(
  ...
  location geography(Point,4326),
  radius double precision NOT NULL,
  ...
)
CREATE INDEX gix_project_location_location 
ON project_location USING gist (location);

该表目前有大约 50,000 条记录。如果我查询该表以查找地理围栏包含一个点的所有 project_locations,例如

SELECT COUNT(*) 
FROM project_location 
WHERE ST_DWithin(location, ST_SetSRID(ST_MakePoint(-84.1000, 34.0000),4326)::geography, radius);

我发现没有使用空间索引。 EXPLAIN 的结果如下:

"Aggregate  (cost=11651.97..11651.98 rows=1 width=0)"
"  ->  Seq Scan on project_location  (cost=0.00..11651.97 rows=1 width=0)"
"        Filter: ((location && _st_expand('0101000020E610000066666666660655C00000000000004140'::geography, radius)) AND ('0101000020E610000066666666660655C00000000000004140'::geography && _st_expand(location, radius)) AND _st_dwithin(location, '0101000020E610000066666666660655C00000000000004140'::geography, radius, true))"

但是,如果半径是一个常数值,如下所示

SELECT COUNT(*) 
FROM project_location 
WHERE ST_DWithin(location, ST_SetSRID(ST_MakePoint(-84.1000, 34.0000),4326)::geography, 1000);

EXPLAIN 使用的空间索引

"Aggregate  (cost=8.55..8.56 rows=1 width=0)"
"  ->  Index Scan using gix_project_location_location on project_location  (cost=0.28..8.55 rows=1 width=0)"
"        Index Cond: (location && '0101000020E610000066666666660655C00000000000004140'::geography)"
"        Filter: (('0101000020E610000066666666660655C00000000000004140'::geography && _st_expand(location, 1000::double precision)) AND _st_dwithin(location, '0101000020E610000066666666660655C00000000000004140'::geography, 1000::double precision, true))"

阅读了 ST_DWithin 如何使用索引后,我明白为什么会这样。本质上,基于半径的边界框用于“预过滤”候选点以确定可能的匹配,然后再对这些点进行相对昂贵的距离计算。

我的问题是有什么方法可以进行这种类型的搜索以便可以使用空间索引?基本上是一种用一堆可变半径地理围栏查询表的方法?

【问题讨论】:

  • 您应该标记并要求将其移至 dba.se。
  • @EvanCarroll dba.se 是什么?为什么要标记?
  • dba.stackexchange.com(以便管理员移动)
  • @EvanCarroll 以及您认为应该搬家的原因是什么?
  • 更专业的 postgresql 和 postgis 用户群

标签: postgresql postgis


【解决方案1】:

PostGIS 允许通过使用功能索引来加快查询速度。我不确定如何在 geography 数据类型中执行此操作,因为那里没有 ST_Expand,但如果您将数据存储在某个墨卡托投影(例如,SRID=3857)中,查询将非常简单。

想法:

  • 围绕您的点生成一个以radius 个单位展开的框;
  • 在这些框上建立索引;
  • 根据这些框查询用户点;
  • 按精确半径重新检查。

在您的project_location 桌子上:

create index on project_location using gist (ST_Expand(location, radius));

现在您可以使用ST_Expand(location, radius),就好像它是您的索引几何列一样。

select count(*) from project_location where ST_Intersects(ST_Expand(location, radius), <your_point>) and ST_Distance(location, <your_point>) < radius;

现在您正在跳过ST_DWithin,因为您希望重新检查永远不要尝试使用索引,并在几何函数上使用您的索引。

对于geography,您可以尝试使用ST_Envelope(ST_Buffer(geom, radius)) 存根ST_Expand。

【讨论】:

    【解决方案2】:

    为了消除简单的事情,你可以尝试将半径投射到double precision,

    SELECT COUNT(*) 
    FROM project_location 
    WHERE ST_DWithin(
      location,
      ST_SetSRID(ST_MakePoint(-84.1000, 34.0000),4326)::geography,
      radius::double precision -- or CAST(radius AS double precision)
    );
    

    同时粘贴输出

    • \dfS ST_DWithin
    • \dfS _ST_DWithin

    【讨论】:

    • 将半径转换为双精度不影响结果(不使用空间索引)。
    • 创建或替换函数 st_dwithin(geography, geography,double precision) RETURNS boolean AS 'SELECT $1 && _ST_Expand($2,$3) AND $2 && _ST_Expand($1,$3) AND _ST_DWithin($1, $2, $3, true)' LANGUAGE sql IMMUTABLE COST 100;
    • 创建或替换函数 _st_dwithin(geography, geography, double precision, boolean) RETURNS boolean AS '$libdir/postgis-2.1', 'geography_dwithin' LANGUAGE c IMMUTABLE STRICT COST 100;
    【解决方案3】:

    我能想到的唯一方法是创建一个fence_contain 表

     geofence_id    point_id
    

    当然,您需要在更新/创建地理围栏和点时触发触发器以保持表格更新。

    【讨论】:

      猜你喜欢
      • 2018-10-22
      • 1970-01-01
      • 2020-10-30
      • 2022-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-03-20
      • 1970-01-01
      相关资源
      最近更新 更多