【问题标题】:Postgres - full table scan too slow - index is not being usedPostgres - 全表扫描太慢 - 未使用索引
【发布时间】:2011-12-09 16:46:31
【问题描述】:

我在 postgres 数据库中有一个包含许多列的表,其中我有:

n_store_object_id     integer,
n_latitude            decimal,
n_longitude           decimal

该表目前大约有 250,000 行。

我需要查找距给定位置固定距离内具有非空 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)

我重新运行上面的查询 - 仍然需要很长时间。 “解释”它表明新创建的索引没有被使用。我错过了什么吗?为什么不使用它,如何强制引擎使用它?哦,首先,这个索引会有帮助吗?

谢谢!

【问题讨论】:

    标签: postgresql optimization


    【解决方案1】:

    您的索引按 ID 排序,然后是 lat,然后是 long。这无济于事,因为它无法确定要搜索的 ID 范围。

    您不能使用传统的“btree”索引(postgres 和所有其他 sql 中的默认值)很好地建立索引。如果您考虑一下这个问题,大多数索引都是基于排序的(按数字或字母顺序)。但是你不能订购地理。您可以按照与单个点的距离顺序排列物品,但是当您移动该点时,有些东西会更近,有些东西会更远,因此顺序会发生变化。

    最佳... 为这个问题创建了特殊的索引。由于您使用的是 postgres,我建议您阅读 GiST。 http://postgis.net/docs/manual-2.0/using_postgis_dbmanagement.html(请谷歌并点击此链接)。

    这现在包含在 postgres 中,专门用于处理地理。

    或者... 第二种解决方案是在数据上放置两个索引,一个 latitute(仅)一个 logditude(仅)。并在另一个答案中提到的查询中添加一个 max 和 min lat 和 long 。 Postgres 可以同时使用这两个索引来缩小范围。请务必使用两个单独的索引,而不是一个同时包含 lat 和 long 的索引。

    【讨论】:

    • GiST 工作得非常好。如果您想做更复杂的地理空间工作,请查看 PostGIS,它为使用 GIS 提供了一些强大的数据类型和 GiST 索引类型。
    • 这似乎最接近我的需要。我在纬度和经度上创建了单独的索引,并在任一方向上将扫描范围限制为 +/- 0.5 度;然后我创建了一个要点索引;但真正缩短查询时间的是:我没有在距离上使用where 子句,而是检查循环内的距离(查询在 plpgsql 函数内)。由于距离是返回值之一,而且我也按此距离排序,所以当我达到所需距离时,我只是中止循环。
    • (续)这很奇怪:不可变函数应该只为每组参数计算一次,但似乎在每次迭代中它们都被计算了多次:返回值一次,一次用于where 子句,一次用于排序。
    • 查询分析器必须认识到重复将会发生以利用它,否则它会浪费更多。您是否尝试过在 lat 和 lng 上编制索引但不包括 object_id 为空的行的部分索引?这似乎更接近我们想要的。
    • 给定的链接已失效。
    【解决方案2】:

    指数并不神奇。默认索引样式只是一个 b-tree,可用于满足对 indexed_key = valueindexed_key &lt; value 等的请求,但仅在一堆列上创建一个不会使基于这些列值的任何表达式立即有效.

    从 9.1 开始,Postgresql 不支持使用索引作为“覆盖索引”来减少执行完整扫描所需的磁盘 I/O 量。 9.2 会。同时,如果您认为这会有所帮助,请使用触发器来填充辅助表,这本质上是相同的,只是没有从查询中自动使用它的糖。但这并不会改变您将为 250,000 行中的每一行进行大量三角计算的事实。

    如果您真的想做这种地理空间索引,请使用立方体/地球距离扩展在坐标上构建 GiST r-tree 索引。这将允许您对“查找此框内的所有点”形式的查询使用索引查找,然后您可以添加其他函数条件以删除框内但目标范围之外的结果。

    【讨论】:

      【解决方案3】:

      您的查询的另一个约束是函数的结果,获得它的唯一方法是对所有非空值执行它。

      只有在你可以减少必须计算的值的范围时才会有一些用处。

      即,如果您可以计算出值得费心计算的最小和最大经纬度。 然后你可以加强约束 与

      and (n_latitude between LaMin and LaMax) and (n_longitude between loMin and loMax)
      

      【讨论】:

      • 我在两者上添加了一个条件 - 它略微缩短了查询时间 - 从大约 4 秒到大约 3.5 秒 - 但仍然需要很长时间。
      【解决方案4】:

      我有类似的设置,并使用标准 PostgreSQL 类型 point 来表示纬度/经度。以下适用于 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。如果是,则查询应该很快。使该框足够大以包含您的所有观点。如果您想摆脱字面上的极端情况,请应用其他标准。这会很便宜。

      【讨论】:

      • 这个盒子不能很好地工作,因为地球坐标系不是平原而是在球体的表面上。球体表面上两点之间的距离几乎不是平面上的距离。由于我的数据包含世界各地的数据点,并且距离测量值(限制)有时非常大(高达一百英里或更多),因此这两个值最终相距甚远。
      • @AleksG:只要您搜索的given distance 不超过100 公里并且您的结果不必精确,这种方法就足够快速且足够好。如前所述,我会求助于 PostGis。
      • @ErwinBrandsetter:不幸的是,我们的一些客户抱怨说“我们和点 x 之间只有 15 英里,而不是您网站上显示的 19 英里”。因此,我们需要获得更精确的函数来计算 geo_distance(其中包含所有三角函数)——它给出的结果在 2,000 英里的距离上只有大约一英里的误差。
      • @AleksG:对于 15 英里,地球的曲率实际上是无关紧要的。您的计算中一定有其他类型的错误。
      • 我在夸大其词。使用当前计算距离的函数,我们得到了一个很好的近似值;我(更重要的是——客户)对结果感到满意。我只是想找到按位置查询对象的最佳方法。不过感谢您的 cmets。
      【解决方案5】:

      您必须创建一个基于函数的索引:

      create index idx_object_distance on objects(fn_geo_distance(51.5, 0, n_latitude, n_longitude))
      

      更新

      就像托尼霍普金森建议的那样,您可以选择的另一个选择是使用 between 过滤范围

      您需要两个单独的索引才能快速实现:

      create index idx_object_latitude on objects(n_latitude);
      create index idx_object_longitude on objects(n_longitude);
      

      数据库将扫描两个索引并在结果上进行合并连接

      【讨论】:

      • 我不能这样做,因为两个参数 51.50 是我的情况下的变量
      • +1 为您的用户名和头像,但答案无济于事 ;)
      猜你喜欢
      • 2014-03-24
      • 2023-03-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多