我在postgres数据库中有一个包含许多列的表,其中包括:
n_store_object_id integer,
n_latitude decimal,
n_longitude decimal
目前该表约有250,000行。
我需要找到位于距给定位置固定距离内的非null store_object_id的记录。对于距离计算,我有以下功能:
CREATE OR REPLACE FUNCTION fn_geo_distance(numeric, numeric, numeric, numeric)
RETURNS numeric AS
$BODY$
declare
lat1d ALIAS for $1;
lon1d ALIAS for $2;
lat2d ALIAS for $3;
lon2d ALIAS for $4;
lat1 DECIMAL := lat1d / 57.29577951;
lon1 DECIMAL := lon1d / 57.29577951;
lat2 DECIMAL := lat2d / 57.29577951;
lon2 DECIMAL := lon2d / 57.29577951;
begin
return 3963.0 * acos(sin(lat1) * sin(lat2) + cos(lat1) * cos(lat2) * cos(lon2 - lon1));
end;$BODY$
LANGUAGE plpgsql IMMUTABLE;
现在,我要求的查询很简单:
select *
from objects
where n_store_object_id is not null
and fn_geo_distance(51.5, 0, n_latitude, n_longitude) <= 20
这需要相当长的时间 - 当我“解释”这个查询时,我可以看到全表扫描。很公平。所以我在这三列上创建了一个索引:
create index idx_object_location on objects(n_store_object_id, n_latitude, n_longitude)
我重新运行上面的查询 - 它仍然需要很长时间。 “解释”它表明没有使用新创建的索引。我错过了什么吗?为什么不使用它,如何强制引擎使用它?哦,首先,这个指数会有帮助吗?
谢谢!
答案 0 :(得分:6)
您的索引按ID排序,然后是纬度,然后是长。这无济于事,因为它无法找出要搜索的ID范围。
使用传统的“btree”索引(postgres和其他所有sql中的默认值)无法对此进行索引。如果您暂时考虑问题,大多数索引都基于订购事物(数字或字母顺序)。但你不能订购地理。您可以按照距离单点的距离顺序排序,但是当您移动该点时,某些事情会更接近,而其他事情会更进一步,以便订单发生变化。
<强>最佳... 强> 为此问题创建了特殊索引。由于您使用的是postgres,我建议您阅读GiST。 http://postgis.net/docs/manual-2.0/using_postgis_dbmanagement.html(请谷歌以及此链接)。
现在将其作为postgres的一部分包含在内,专门用于处理地理位置。
<强> Alternativly ... 强> 第二种解决方案是在数据上放置两个索引,一个(仅)一个logditude(仅)。并在查询中添加max和min lat和long,如另一个答案中所述。 Postgres可以使用BOTH索引来缩小范围。重要的是使用两个单独的索引,而不是包含lat和long的索引。
答案 1 :(得分:2)
指数并不神奇。默认索引样式只是一个b树,可以用来满足indexed_key = value
,indexed_key < value
等的请求,但只是在一堆列上创建一个不会根据这些创建任何表达式列值立即有效。
Postgresql,从9.1开始,不支持使用索引作为“覆盖索引”来减少执行完整扫描所需的磁盘I / O量。 9.2会。与此同时,如果你认为这会带来好处,可以使用触发器来保持一个辅助表的填充,这基本上是相同的,只是没有从查询中自动使用它的糖。但这并没有改变你将为250,000行中的每一行做一堆三角运算的事实。
如果您确实想要执行此类地理空间索引,请使用cube / earthdistance扩展在坐标上构建GiST r-tree索引。这将允许您对“查找此框中的所有点”形式的查询使用索引查找,然后您可以添加其他函数条件来修剪框中但在目标范围之外的结果。
答案 2 :(得分:1)
您的查询的其他约束是函数的结果,唯一的方法是为所有非空值执行它。
如果您可以减少必须计算的值范围,那么它只会有一些用处。
即如果你可以计算出值得打扰计算的最小和最大长度和纬度。 然后你可以加强约束 随着
and (n_latitude between LaMin and LaMax) and (n_longitude between loMin and loMax)
答案 3 :(得分:1)
我有类似的设置,并使用标准的PostgreSQL类型point
作为lat / lon。以下适用于PostgreSQL 8.4 +。
CREATE table object(
object_id serial PRIMARY KEY
,geocode point
);
然后我添加一个像这样的GIST索引:
CREATE INDEX object_geocode_idx
ON object
USING gist (box(geocode, geocode));
请注意我如何索引由两个点组成的虚拟框 - 在索引的情况下相同的两个点。
另外,我集群我的表在该索引上,因此必须获取最少的块。
ALTER TABLE object CLUSTER ON object_geocode_idx;
现在,尝试这样的搜索:
SELECT *
FROM object
WHERE box(geocode,geocode) <@ box(mypoint1, mypoint2);
了解"contained in" operator in the manual
如果索引被使用,请与EXPLAIN ANALYZE
一起检查。如果是,查询应该快速闪电。使那个盒子足够大,以包括你所有的积分。如果您想摆脱文字角落案例,请应用其他条件。这样便宜。
答案 4 :(得分:0)
您必须创建基于函数的索引:
create index idx_object_distance on objects(fn_geo_distance(51.5, 0, n_latitude, n_longitude))
更新
像Tony Hopkinson建议的那样,你有另一个选择是使用之间的来过滤范围您需要两个单独的索引来快速实现:
create index idx_object_latitude on objects(n_latitude);
create index idx_object_longitude on objects(n_longitude);
数据库将扫描两个索引并在结果上执行合并连接