4

我正在与一个正在绘制美国太阳能潜力的非营利组织合作。不用说,我们有一个大得离谱的 PostgreSQL 9 数据库。运行如下所示的查询会很快,直到order by取消注释该行,在这种情况下,相同的查询需要永远运行(185 毫秒没有排序与 25 分钟相比)。应采取哪些步骤来确保此查询和其他查询在更易于管理和合理的时间内运行?

select  A.s_oid, A.s_id, A.area_acre, A.power_peak, A.nearby_city, A.solar_total 
from global_site A cross join na_utility_line B
where (A.power_peak between 1.0 AND  100.0)
and A.area_acre >= 500
and A.solar_avg >= 5.0
AND A.pc_num <= 1000
and (A.fips_level1 = '06'  AND A.fips_country = 'US' AND A.fips_level2 = '025')
and B.volt_mn_kv >= 69
and B.fips_code like '%US06%'
and B.status = 'active'
and ST_within(ST_Centroid(A.wkb_geometry), ST_Buffer((B.wkb_geometry), 1000))
--order by A.area_acre
offset 0 limit 11;
4

7 回答 7

5

排序不是问题——事实上,排序的 CPU 和内存成本接近于零,因为 Postgres 具有 Top-N 排序,其中扫描结果集同时保持最新的小型排序缓冲区仅保存 Top-N 行.

select count(*) from (1 million row table)               -- 0.17 s
select * from (1 million row table) order by x limit 10; -- 0.18 s
select * from (1 million row table) order by x;          -- 1.80 s

因此,您会看到 Top-10 排序仅将 10 毫秒添加到愚蠢的快速计数(*),而对于真正的排序则更长。这是一个非常简洁的功能,我经常使用它。

好的,现在没有 EXPLAIN ANALYZE 是不可能确定的,但我的感觉是真正的问题是交叉连接。基本上,您使用以下方法过滤两个表中的行:

where (A.power_peak between 1.0 AND  100.0)
and A.area_acre >= 500
and A.solar_avg >= 5.0
AND A.pc_num <= 1000
and (A.fips_level1 = '06'  AND A.fips_country = 'US' AND A.fips_level2 = '025')

and B.volt_mn_kv >= 69
and B.fips_code like '%US06%'
and B.status = 'active'

好的。我不知道在两个表中选择了多少行(只有 EXPLAIN ANALYZE 会告诉我),但这可能很重要。知道这些数字会有所帮助。

然后我们得到了有史以来最糟糕的 CROSS JOIN 条件:

and ST_within(ST_Centroid(A.wkb_geometry), ST_Buffer((B.wkb_geometry), 1000))

这意味着 A 的所有行都与 B 的所有行匹配(因此,这个表达式将被计算很多次),使用一堆非常复杂、缓慢且 CPU 密集型的函数。

当然慢得可怕!

当您删除 ORDER BY 时,postgres 刚出现(偶然?)在开始时有一堆匹配的行,输出这些行,并在达到 LIMIT 后停止。

这是一个小例子:

表 a 和 b 相同,包含 1000 行和 BOX 类型的列。

select * from a cross join b where (a.b && b.b)     --- 0.28 s

这里 1000000 个框重叠(运算符 &&)测试在 0.28 秒内完成。生成测试数据集,因此结果集仅包含 1000 行。

create index a_b on a using gist(b);
create index b_b on a using gist(b);
select * from a cross join b where (a.b && b.b)     --- 0.01 s

这里使用索引来优化交叉连接,速度快得离谱。

您需要优化该几何匹配。

  • 添加将缓存的列:
    • ST_Centroid(A.wkb_geometry)
    • ST_Buffer((B.wkb_geometry), 1000)

在您的 CROSS JOIN 期间重新计算这些慢速函数一百万次是没有意义的,因此将结果存储在列中。使用触发器使它们保持最新。

  • 添加将缓存的 BOX 类型的列:

    • ST_Centroid(A.wkb_geometry)的边界框
    • ST_Buffer的边界框((B.wkb_geometry), 1000)
  • 在 BOX 上添加要点索引

  • 添加将使用索引的 Box 重叠测试(使用 && 运算符)

  • 保留您的 ST_Within ,它将作为通过行的最终过滤器

也许您可以只索引 ST_Centroid 和 ST_Buffer 列...并使用(索引)“包含”运算符,请参见此处:

http://www.postgresql.org/docs/8.2/static/functions-geometry.html

于 2011-05-03T21:27:29.503 回答
2

我建议在 area_acre 上创建一个索引。您可能想看看以下内容: http ://www.postgresql.org/docs/9.0/static/sql-createindex.html

不过,我建议在高峰时间做这种事情,因为这对于大量数据来说可能有点密集。对于索引,您还必须注意的一件事是按计划重建它们以确保随着时间的推移性能。同样,这个时间表应该在高峰时间之外。

您可能想看看 SO'er 的这篇文章以及他在使用索引时数据库随时间变慢的经验: 为什么 PostgresQL 查询性能随时间下降,但在重建索引时恢复

于 2011-05-03T17:56:08.920 回答
1

如果 A.area_acre 字段没有被索引,可能会减慢它的速度。您可以使用 EXPLAIN 运行查询,以查看它在执行期间正在做什么。

于 2011-05-03T17:57:03.913 回答
0

首先我会看看创建索引,确保你的数据库被清理,增加你的数据库安装的共享缓冲区,work_mem 设置。

于 2011-05-03T17:55:38.097 回答
0

首先要看的是您在订购的字段上是否有索引。如果没有,添加一个将显着提高性能。我不太了解postgresql,但类似于:

CREATE INDEX area_acre ON global_site(area_acre)

正如其他回复中所指出的,在处理大型数据集时,索引过程是密集的,所以在非高峰期这样做。

于 2011-05-03T17:56:57.970 回答
0

我不熟悉 PostgreSQL 优化,但听起来当使用 ORDER BY 子句运行查询时发生的事情是创建整个结果集,然后对其进行排序,然后从中获取前 11 行排序的结果。如果没有 ORDER BY,查询引擎可以按照它喜欢的任何顺序生成前 11 行,然后就完成了。

根据结果​​集的构建方式,在area_acre字段上建立索引很可能对排序(ORDER BY)没有帮助。理论上,它可以用于通过使用;global_site上的索引遍历表来生成结果集。area_acre在这种情况下,将以所需的顺序生成结果(并且它可能会在结果中生成 11 行后停止)。如果它没有按该顺序生成结果(看起来可能不是),那么该索引将无助于对结果进行排序。

您可能会尝试的一件事是从查询中删除“CROSS JOIN”。我怀疑这会有所作为,但值得一试。因为 WHERE 子句涉及连接两个表(通过 ST_WITHIN),所以我相信结果与内部连接相同。使用 CROSS JOIN 语法可能会导致优化器做出不希望的选择。

否则(除了确保要过滤的字段存在索引之外),您可以对查询进行一些猜谜游戏。一个突出的条件是area_acre >= 500. 这意味着查询引擎正在考虑满足该条件的所有行。但随后只取前 11 行。您可以尝试将其更改为area_acre >= 500 and area_acre <= somevalue. 这somevalue是需要调整以确保您至少获得 11 行的猜测部分。然而,这似乎是一件很俗气的事情,所以我有点沉默地提到它。

于 2011-05-03T20:12:08.573 回答
0

您是否考虑过创建基于表达式的索引以获得更复杂的连接和 where 条件?

于 2014-03-27T14:34:05.443 回答