8

Postgresql 9.2 システムで、通常の形式で約 20 秒かかるクエリがありますが、CTE を使用すると 120 ミリ秒しかかかりません。

簡潔にするために、両方のクエリを単純化しました。

通常の形式は次のとおりです (約 20 秒かかります)。

SELECT *
FROM tableA
WHERE (columna = 1 OR columnb = 2) AND
    atype = 35 AND
    aid IN (1, 2, 3)
ORDER BY modified_at DESC
LIMIT 25;

このクエリの説明は次のとおりです。 http://explain.depesz.com/s/2v8

CTE フォーム (約 120ms):

WITH raw AS (
    SELECT *
    FROM tableA
    WHERE (columna = 1 OR columnb = 2) AND
        atype = 35 AND
        aid IN (1, 2, 3)
)
SELECT *
FROM raw
ORDER BY modified_at DESC
LIMIT 25;

CTE の説明は次のとおりです: http://explain.depesz.com/s/uxy

をクエリの外側に移動するだけORDER BYで、コストが 99% 削減されます。

2 つの質問があります。1) CTE を使用せずに最初のクエリを作成し、論理的に同等のパフォーマンスを実現する方法はありますか?2) パフォーマンスのこの違いは、プランナーがフェッチ方法を決定する方法について何を示していますか?データ?

上記の質問に関して、最初のクエリのパフォーマンスを向上させるのに役立つ追加の統計情報やその他のプランナーのヒントはありますか?


編集:制限を取り除くと、クエリは逆方向のインデックス スキャンではなくヒープ スキャンを使用するようになります。クエリがなければ、LIMIT40 ミリ秒で完了します。

の効果を見た後、 、 などLIMITで試しました。クエリは、使用すると 100 ミリ秒未満で実行され、 > 1では 10 秒以上実行されます。LIMIT 1LIMIT 2LIMIT 1LIMIT

これについてもう少し考えた後、質問 2 は、なぜプランナーが逆方向のインデックス スキャンを使用し、別の論理的に同等のケースでビットマップ ヒープ スキャン + ソートを使用するのかという問題に要約されます。そして、プランナーが両方のケースで効率的な計画を使用するのをどのように「助ける」ことができますか?


更新: 最も包括的で役立つ Craig の回答を受け入れました。私が最終的に問題を解決した方法は、論理的には同等ではありませんが、実質的に同等であるクエリを使用することでした。問題の根底にあるのは、modified_at のインデックスを逆方向にスキャンすることでした。これは良い考えではなかったことをプランナーに知らせるために、フォームの述語を追加しWHERE modified_at >= NOW() - INTERVAL '1 year'ます。これには、アプリケーション用の十分なデータが含まれていましたが、プランナーが逆方向のインデックス スキャン パスをたどることができませんでした。

これは、サブクエリまたは CTE のいずれかを使用してクエリを書き直す必要をなくす、はるかに影響の少ないソリューションでした。YMMV。

4

2 に答える 2

10

これが発生する理由は次のとおりです。次の説明は少なくとも 9.3 まで有効です (これを読んでいて新しいバージョンを使用している場合は、変更されていないことを確認してください)。

PostgreSQL は CTE の境界を越えて最適化されません。各 CTE 句は分離して実行され、その結果はクエリの他の部分によって消費されます。したがって、次のようなクエリです。

WITH blah AS (
    SELECT * FROM some_table
)
SELECT *
FROM blah
WHERE id = 4;

完全な内部クエリが実行されます。PostgreSQL は、id = 4修飾を内部クエリに「プッシュ ダウン」しません。その点で、CTE は「最適化フェンス」であり、良いことも悪いこともあります。必要に応じてプランナーをオーバーライドできますが、FROMプッシュダウンが必要な場合に、深くネストされたサブクエリチェーンの単純な構文クリーンアップとして CTE を使用することはできません。

上記を次のように言い換えると:

SELECT *
FROM (SELECT * FROM some_table) AS blah
WHERE id = 4;

CTE の代わりにサブクエリを使用するFROMと、Pg は qual をサブクエリにプッシュし、すべてがうまくすばやく実行されます。

お気づきのように、これは、クエリ プランナーが不適切な決定を下した場合にも役立ちます。あなたの場合、テーブルの逆方向インデックススキャンは、ビットマップまたは2つの小さなインデックスのインデックススキャンとそれに続くフィルターとソートのほうが非常に高価であるように見えますが、プランナーはそうなるとは思わないため、スキャンするクエリを計画しますインデックス。

CTEを使用すると、内部クエリにプッシュできないため、ORDER BYその計画をオーバーライドし、劣った実行計画であると考えられるものを使用するように強制しますが、はるかに優れていることが判明しました。

ハックと呼ばれるこれらの状況に使用できる厄介な回避策がありOFFSET 0ますが、プランナーに正しいことをさせる方法がわからない場合にのみ使用する必要があります-使用する必要がある場合は、これを沸騰させてください自己完結型のテストケースに落とし込み、クエリプランナーのバグの可能性として PostgreSQL メーリングリストに報告してください。

代わりに、プランナーが間違った決定を下している理由を最初に確認することをお勧めします。

最初の候補は統計/推定の問題です。問題のあるクエリ プランを確認すると、予想される結果行の 3500 倍の推定ミスがあることがわかります。これは大きいですが、ありえないほど大きいわけではありませんが、プランナが重要な行セットを期待している場合に、実際には 1 つの行しか得られないことは興味深いことです。ただし、これはあまり役に立ちません。行数が予想よりも少ない場合は、インデックスの使用を選択したことが予想よりも優れていたことを意味します。

主な問題は、より小さく、より選択的なインデックスを使用していないように見えます。これは、インデックスを逆方向にスキャンしてソートを回避することで、実際よりも多くの時間を節約できると考えているためです。並べ替える一致する行が 1 つしかないことを考えると、これは理にかなっています。予想される 3500 行を取得した場合は、並べ替えを回避する方が理にかなっている可能性がありますが、メモリ内で並べ替えるにはまだかなり小さい行セットです。sierra_kilopapa_limaORDER BY

などのパラメータを設定しますenable_seqscanか? その場合は、設定を解除してください。これらはテスト専用であり、本番環境での使用にはまったく適していません。パラメータを使用していない場合は、enable_これを PostgreSQL メーリング リストで取り上げる価値があると思いますpgsql-perform。ただし、匿名化されたプランはこれを少し難しくします。特に、あるプランの識別子が他のプランの同じオブジェクトを参照するという保証がなく、質問のクエリに書いたものと一致しないためです。メーリング リストで質問する前に、すべてが一致する、適切に手作業で作成したバージョンを作成する必要があります。

誰かが助けてくれる本当の価値を提供しなければならない可能性は十分にあります。公開メーリング リストでこれを行いたくない場合は、別のオプションを利用できます。(私のプロフィールによると、私はそのうちの1つで働いていることに注意してください)。

于 2013-07-11T01:43:34.033 に答える
2

暗闇でのショットですが、これを実行するとどうなりますか

SELECT *
FROM (
    SELECT *
    FROM tableA
    WHERE (columna = 1 OR columnb = 2) AND
        atype = 35 AND
        aid IN (1, 2, 3)
) AS x
ORDER BY modified_at DESC
LIMIT 25;
于 2013-07-11T01:35:55.203 に答える