为什么这个功能成倍增加?

时间:2013-04-21 22:31:28

标签: postgresql plpgsql postgresql-performance

如果我创建一个循环执行一堆动态查询的函数,那么处理时间似乎要大得多。为了举例,我将使用以下代码。请记住,我必须在我的代码中使用执行语句。

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语句循环10x,则需要125ms才能完成。 如果我的for语句循环100x,则需要4,250ms才能完成。

是否有我可以使用的设置,以便100x的循环将在1,250ms完成?

编辑:更多信息

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

EDIT2:

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

2 个答案:

答案 0 :(得分:1)

为什么放缓?

计算符合x < 100的行的平均值显然x < 1的计算费用高得多。多少,我们不知道,你的问题中没有没有

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

FOR i IN 90..100 LOOP ...

VS

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;

答案 1 :(得分:0)

首先,我想回应克雷格对真实信息的要求。根据我的经验,基于非常细节的细节,循环变得指数级变慢。我不知道这是否会回答这个问题,但我会举一个例子,我在自己的工作中遇到过。如果没有其他任何内容,它将为解决此问题提供一个好的示例。

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

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

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

如果您的情况完全像您所说的那样,PostgreSQL将更有效地缓存第一行而不是最后一行。此外,您将最终得到以下内容:

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

在你的plperl编辑中提出的第三种可能性是你可能正在计划重新使用几行的计划,这些计划包含更多行,直到计划不再合理为止。注意,如果要丢失操作系统预读缓存,则要访问大部分表时,仅扫描索引不一定便宜。

虽然不可能看到真正的问题,但却看不到真正的代码。以上是黑暗中的镜头或要检查的东西。