0

如果我创建一个循环执行一堆动态查询的函数,处理时间似乎会成倍增加。举个例子,我将使用以下代码。请记住,我必须在我的代码中使用执行语句。

FOR i IN 0..10 LOOP
EXECUTE 'SELECT AVG(val) FROM some_table where x < '||i INTO count_var;
IF count_var < 1 THEN
INSERT INTO some_other_table (vals) VALUES (count_var);
END IF;
END LOOP;

如果我的 for 语句循环 10 次,则需要 125 毫秒才能完成。如果我的 for 语句循环 100 次,则需要 4,250 毫秒才能完成。

有没有我可以使用的设置,以便在 1,250 毫秒内完成 100 次循环?

编辑:更多信息

PostgreSQL 9.2.4 on x86_64-unknown-linux-gnu, compiled by gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3, 64-bit

每个执行查询都在进行仅索引扫描。这是计划。

 Aggregate  (cost=85843.94..85843.94 rows=1 width=8) (actual time=1241.941..1241.944 rows=1 loops=1)
   ->  Index Only Scan using some_table_index on some_table  (cost=0.00..85393.77 rows=300114 width=8) (actual time=0.046..1081.718 rows=31293 loops=1)
         Index Cond: ((x > 1) AND (y < 1))
         Heap Fetches: 0
 Total runtime: 1242.012 ms

编辑2:

我用plperl重写了这个函数。当我在 100x 执行查询上使用“spi_exec_query()”时,它运行了 4,250 毫秒。当我在 100 倍执行查询上使用“spi_query()”时,它在 1,250 毫秒内运行 - 消除了指数增长。

4

2 回答 2

1

为什么减速?

计算符合条件的行的平均值x < 100显然计算相同的x < 1. 多少,我们不知道,您的问题中没有任何内容。

在不知道你表中的数据分布的情况下,我们只能猜测。可能有 5 行x = 5, 可能有 5M 行x = 77。测试:

FOR i IN 90..100 LOOP ...

对比

FOR i IN 0..10 LOOP ...

并考虑从

SELECT x, count(*) FROM some_table WHERE x < 100 GROUP BY 1;

此外,比较两个数据点也很难成为声称“指数增长”的理由。在评论中,您推测 Postgres 可能开始写入磁盘,这可能只是解释它。

普通 SQL 替代方案

无论哪种方式,您的问题都无法支持您的主张:

我必须使用执行语句

你真的?这个简单的 SQL 语句与您的 PL/pgSQL 片段完全相同,但可能要快得多:

INSERT INTO some_other_table (vals)
SELECT avg_val_by_x
FROM  (
    SELECT avg(val) OVER (ORDER BY x) AS avg_val_by_x
    FROM   some_table
    WHERE  x < 10
    ) sub
WHERE  avg_val_by_x < 1;
于 2013-04-24T02:42:41.500 回答
0

首先,我想回应克雷格对真实信息的要求。以我的经验,循环会因非常细微的细节而呈指数级变慢。我不知道这是否会回答这个问题,但我会举一个我在自己的工作中遇到的例子。如果不出意外,它将提供一个很好的例子,说明在解决此问题时要寻找的东西。

在 LedgerSMB 中批量支付功能的早期版本中,我们将遍历发票(以二维数组的形式出现)。然后,我们将为每张发票插入两行,然后更新第三行。对于 10 张发票,这将很快。对于 100,会出现明显的减速,而对于 1000(是的,这可能会发生,1000 张发票一次支付给供应商),系统将花费很长时间(以小时为单位)。

问题与缓存有关。系统将有效地开始丢失缓存,并且这些缓存的频率会增加,直到每次写入都有效,新的随机磁盘 I/O 位。因此,随着循环变大,系统会变慢。

我们的解决方案是将所有行写入一个临时表,然后根据临时表的内容运行两次插入查询,最后根据相同的内容运行一次更新。这将时间从几小时缩短到一两分钟。

如果您的情况与您所说的完全一样,那么 PostgreSQL 将比最后一行更有效地缓存第一行。此外,您将得到以下结果:

其中 i 为 1,答案为 a1,其中 i 为 2,答案为 (a1 + a2)/2,其中 i 为 3,答案为 (a1 + a2 + a3)/3,依此类推。所以你有缓存问题和计算问题。

在您的 plperl 编辑中提出的第三种可能性是,您可能会获得一个计划,该计划将几行重新用于具有更多行的计划,以至于该计划不再有意义。请注意,如果要访问表的大部分,则仅索引扫描不一定便宜,因为您丢失了操作系统预读缓存。

如果没有看到真正的代码,虽然不可能看到真正的问题是什么。以上是在黑暗中拍摄或需要检查的东西。

于 2013-04-25T15:10:34.913 回答