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 の 1 つの解決策は、 PostgreSQL 9.2+ にアップグレードすることであり、すべてが自動的に正常に機能するはずです。

于 2013-05-03T16:01:54.567 に答える
6

クエリ プランナー / オプティマイザーは、定数が手元にあれば、より適切なプランを計算できます。

定数が使用されていない場合、プランナは および のすべての可能な値に対して受け入れられる計画を生成する必要がstartdateありenddateます。これら 2 つの値の差が非常に大きい場合は、テーブルの大部分をフェッチする必要があります。この場合、ランダム アクセスのコストが線形読み取りよりも高いため、ほとんどの場合、インデックスは使用されません。

ただし、定数がある場合、プランナーは収集された統計に基づいて計算できます。クエリはテーブルのごく一部にしか触れないため、インデックスの方が高速になる可能性があります。

これは、PostgreSQL クエリ プランナーの一般的な問題です。マニュアルのPREPAREセクションにいくつかのヒントが含まれています ( PREPAREpl/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;

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