0

一連の動的クエリを実行してループする関数を作成すると、処理時間が指数関数的に大きくなるようです。例として、次のコードを使用します。コードでexecuteステートメントを使用する必要があることに注意してください。

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 ミリ秒かかります。

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,250 ミリ秒で実行され、指数関数的な増加がなくなりました。

4

2 に答える 2

1

減速の理由は?

に該当する行の平均を計算することは、同じことを計算するよりもはるかにx < 100コストがかかることは明らかです。あなたの質問には何もありません。x < 1

テーブル内のデータ分布がわからない場合は、推測するしかありません。x = 5の場合は 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;

また、2 つのデータ ポイントを比較することは、「指数関数的成長」を主張する根拠にはなりません。コメントでは、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

最初に、本当の情報を求める Craig の要求に応えたいと思います。私の経験では、非常に重要な詳細に基づいて、ループは指数関数的に遅くなります。これが質問に答えるかどうかはわかりませんが、自分の仕事で出くわした例を挙げます。他に何もないとしても、この問題をトラブルシューティングする際に探すべきものの良い例を示します。

LedgerSMB の一括支払い関数の以前の具体化では、請求書 (2 次元配列として入ってくる) をループ処理していました。次に、請求書ごとに 2 つの行を挿入し、3 つ目の行を更新します。10 件の請求書の場合、これは高速です。100 の場合は速度が著しく低下し、1000 の場合 (1000 の請求書が一度にベンダーに支払われる可能性があります)、システムに長い時間がかかります (数時間)。

問題はキャッシングに関係していました。システムは効果的にキャッシュの欠落を開始し、すべての書き込みが効果的にランダムなディスク I/O の新しいビットになるまで、これらの頻度が増加します。その結果、ループが大きくなると、システムの速度が低下します。

私たちの解決策は、すべての行を一時テーブルに書き込んでから、一時テーブルの内容に基づいて 2 つの挿入クエリを実行し、最後に同じ内容に基づいて 1 つの更新を実行することでした。これにより、時間が数時間から 1 ~ 2 分程度に短縮されました。

あなたのケースがあなたが言っていることとまったく同じである場合、PostgreSQL は最後の行よりも最初の行をより効果的にキャッシュします。さらに、次のようになります。

i が 1 の場合、答えは a1、i が 2 の場合、答えは (a1 + a2)/2、i が 3 の場合、(a1 + a2 + a3)/3 となります。したがって、キャッシュの問題と計算の問題の両方があります。

plperl の編集で提起された 3 つ目の可能性は、より多くの行を含む計画に再利用されるいくつかの行の計画を取得し、その計画が意味をなさなくなる可能性があることです。OS 先読みキャッシュが失われるため、テーブルの大部分にアクセスする場合、インデックスのみのスキャンは必ずしも安価ではないことに注意してください。

実際の問題が何であるかを理解することは不可能ですが、実際のコードを見なければ。上記は暗闇でのショットまたは確認事項です。

于 2013-04-25T15:10:34.913 に答える