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;

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

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 的新手,所以我可能缺少一些东西,但我似乎无法在任何地方找到答案。

4

3 回答 3

7

@AH 的解释对于PostgreSQL 9.1 或更早版本是准确的。因此它适用于使用过时版本 8.3 的 OP。

然而,PostgreSQL 9.2在这方面带来了实质性的更新。PL/pgSQL 函数对于何时重新计划变得更加智能。我在这里引用 9.2 的发行说明

E.5.3.1.3。优化器

即使在使用准备好的语句时,也允许计划器为特定参数值生成自定义计划 (Tom Lane)

过去,准备好的语句总是有一个用于所有参数值的“通用”计划,这通常比用于包含显式常量值的非准备语句的计划差很多。现在,计划器尝试为特定参数值生成自定义计划。只有在定制计划反复证明没有任何好处后,才会使用通用计划。此更改应消除以前使用准备好的语句(包括 PL/pgSQL 中的非动态语句)所带来的性能损失。

大胆强调我的。

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

于 2013-05-03T16:01:54.567 回答
6

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

当不使用常量时,规划器必须生成一个对和的所有可能值都可接受的规划。如果这两个值之间的差异很大,则必须提取表的很大一部分。在这种情况下,大多数情况下不使用 indizes,因为随机访问成本高于线性读取。startdateenddate

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

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

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

于 2013-05-03T13:52:25.383 回答
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;

让它在 8.3 中工作而无需using进行字符串连接:

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;
于 2013-05-03T14:02:13.653 回答