使用输入变量时,Postgres功能要慢得多

时间:2013-05-03 13:17:25

标签: performance function postgresql plpgsql postgresql-8.3

我在Postgres 8.3.5中有一个函数,它从多个表中选择数据并将结果转储到一个表中:

create or replace function test_function_2(startdate timestamp, enddate timestamp)
returns void as $$
begin
     delete from cl_final_report;

     INSERT INTO cl_final_report
     SELECT 
         b.batchkey AS batchnumber, 
         pv.productkey, 
         p.name AS productname, 
         avg(r.value) AS avgchemlean, 
         sum(r.auxvalue) AS totalweight, 
         max(o.time) AS timecompleted
     FROM result r
     LEFT JOIN physicalvalue pv ON r.physicalvaluekey = pv.physicalvaluekey
     LEFT JOIN product p ON pv.productkey = p.productkey
     LEFT JOIN object o ON r.objectkey = o.objectkey
     LEFT JOIN batch b ON o.batchkey = b.batchkey
     WHERE pv.name = 'CL'::text AND
         and o.time between startdate and enddate
     GROUP BY b.batchkey, pv.productkey, p.name
end
$$ language plpgsql;

此功能需要113秒才能完成,使用PgAdmin并执行此命令:

select test_function_2('05/02/2013', '05/03/2013')

但是,如果我用这样的文字替换函数中的输入变量:

create or replace function test_function_2(startdate timestamp, enddate timestamp)
returns void as $$
begin
     delete from cl_final_report;

     INSERT INTO cl_final_report
     SELECT 
         b.batchkey AS batchnumber, 
         pv.productkey, 
         p.name AS productname, 
         avg(r.value) AS avgchemlean, 
         sum(r.auxvalue) AS totalweight, 
         max(o.time) AS timecompleted
     FROM result r
     LEFT JOIN physicalvalue pv ON r.physicalvaluekey = pv.physicalvaluekey
     LEFT JOIN product p ON pv.productkey = p.productkey
     LEFT JOIN object o ON r.objectkey = o.objectkey
     LEFT JOIN batch b ON o.batchkey = b.batchkey
     WHERE pv.name = 'CL'::text AND
         and o.time between '05/02/2013' and '05/03/2013'
     GROUP BY b.batchkey, pv.productkey, p.name
end
$$ language plpgsql;

该功能在不到5秒的时间内执行。

我是Postgres的新手,所以我可能会缺少一些东西,但我似乎无法在任何地方找到答案。

3 个答案:

答案 0 :(得分:7)

@A.H's explanation对于 PostgreSQL 9.1或更早版是准确的。就是这样 适用于使用过时版本8.3的OP。

然而, PostgreSQL 9.2 在这方面带来了实质性的更新。关于何时重新计划,PL / pgSQL函数变得更加智能。我引用了release notes for 9.2 here

  

E.5.3.1.3。优化

     

允许计划程序为特定参数值生成自定义计划,即使使用预准备语句(Tom Lane)

也是如此      

过去,准备好的语句总是有一个“通用”计划,用于所有参数值,这通常很多   不如用于包含的非预备陈述的计划   显式常量值。现在,计划者试图生成自定义   计划特定的参数值。只会使用通用计划   经过多次证明定制计划无法提供任何好处。这个   改变应该消除以前看到的绩效惩罚   使用准备好的陈述(包括非动态陈述   PL / pgSQL的)。

大胆强调我的。

Ergo:OP 的一个解决方案是升级到PostgreSQL 9.2+,一切都应该自动正常工作。

答案 1 :(得分:6)

查询规划器/优化器可以在手头有常量时计算出更好的计划。

如果没有使用常量,则规划人员必须生成一个计划,该计划可用于startdateenddate所有可能值。如果这两个值之间的差异非常大,则必须获取表的大部分内容。在这种情况下,在大多数情况下不使用indizes,因为随机访问成本高于线性读取。

但是当有常量时,计划员可以根据收集的统计数据计算,查询只会触及表中的一小部分,因此索引可能会更快。

这是PostgreSQL查询规划器的常见问题。本手册包含PREPARE部分中的一些提示(PREPARE由pl / pgsql内部使用):

  

在某些情况下,为准备好的语句生成的查询计划将不如在语句已正确提交和执行时选择的查询计划。这是因为在计划语句并且计划程序尝试确定最佳查询计划时,语句中指定的任何参数的实际值都不可用。 PostgreSQL收集有关表中数据分布的统计信息,并且可以在语句中使用常量值来猜测执行语句的可能结果。由于在计划带参数的预准备语句时此数据不可用,因此所选计划可能不是最理想的。要检查PostgreSQL为预准备语句选择的查询计划,请使用EXPLAIN。

答案 2 :(得分:4)

如果您使查询动态化,您可以在每次执行时强制执行新计划:

create or replace function test_function_2(
    startdate timestamp, enddate timestamp
) returns void as $function$
begin
    delete from cl_final_report;

    execute $$
        INSERT INTO cl_final_report
        SELECT 
            b.batchkey AS batchnumber, 
            pv.productkey, 
            p.name AS productname, 
            avg(r.value) AS avgchemlean, 
            sum(r.auxvalue) AS totalweight, 
            max(o.time) AS timecompleted
        FROM result r
        LEFT JOIN physicalvalue pv ON r.physicalvaluekey = pv.physicalvaluekey
        LEFT JOIN product p ON pv.productkey = p.productkey
        LEFT JOIN object o ON r.objectkey = o.objectkey
        LEFT JOIN batch b ON o.batchkey = b.batchkey
        WHERE pv.name = 'CL'::text AND
            and o.time between $1 and $2
        GROUP BY b.batchkey, pv.productkey, p.name
    $$ using startdate, enddate;
end;
$function$ language plpgsql;

要在没有using的情况下在8.3中工作,请进行字符串连接:

create or replace function test_function_2(
    startdate timestamp, enddate timestamp
) returns void as $function$
begin
    delete from cl_final_report;

    execute $$
        INSERT INTO cl_final_report
        SELECT 
            b.batchkey AS batchnumber, 
            pv.productkey, 
            p.name AS productname, 
            avg(r.value) AS avgchemlean, 
            sum(r.auxvalue) AS totalweight, 
            max(o.time) AS timecompleted
        FROM result r
        LEFT JOIN physicalvalue pv ON r.physicalvaluekey = pv.physicalvaluekey
        LEFT JOIN product p ON pv.productkey = p.productkey
        LEFT JOIN object o ON r.objectkey = o.objectkey
        LEFT JOIN batch b ON o.batchkey = b.batchkey
        WHERE pv.name = 'CL'::text AND
            and o.time between '$$ || startdate || $$' and '$$ || enddate || $$'
        GROUP BY b.batchkey, pv.productkey, p.name
    $$;
end;
$function$ language plpgsql;