次のクエリにインデックスを付ける方法はありますか?
SELECT run_id, MAX ( frame ) , MAX ( time ) FROM run.frames_stat GROUP BY run_id;
frame
とtime
にソートされた(非複合)インデックス、およびrun_id
にインデックスを作成しようとしましたが、クエリプランナーはそれらを使用しません。
その他の情報:
frames_stat
テーブルには4200万行ありますスキーマ:
CREATE TABLE run.frame_stat (
id bigint NOT NULL,
run_id bigint NOT NULL,
frame bigint NOT NULL,
heap_size bigint NOT NULL,
"time" timestamp without time zone NOT NULL,
CONSTRAINT frame_stat_pkey PRIMARY KEY (id)
)
分析の説明:
HashAggregate (cost=1086240.000..1086242.800 rows=280 width=24) (actual time=14182.426..14182.545 rows=280 loops=1)
Group Key: run_id
-> Seq Scan on zulu (cost=0.000..770880.000 rows=42048000 width=24) (actual time=0.037..4077.182 rows=42048000 loops=1)
クエリをまったく変更できない場合、それはtoo badです。良い解決策は得られません。テーブル修飾していない場合は、テーブル(run.
frames_stat
)、別のスキーマ(または一時的なもの)に同じ名前でマテリアライズドビュー(以下を参照)を作成し、 search_path
(オプションで、これが望ましいセッションのみ)-非常に優れたパフォーマンス。
このようなテクニックのレシピは次のとおりです。
@ Joishiのアイデア にRULE
を付けると、(絶望的な)最後の手段の目安になります。しかし、私はむしろそこに行きたくない。予期しない動作による落とし穴が多すぎます。
クエリを変更できる場合は、ルーズインデックススキャンをエミュレートする必要があります。
これは、クエリを別の個別のrun_id
を含むテーブルに基づいて実行できる場合、さらに効率的になります。これをrun_tbl
と呼びましょう。まだ作成していない場合は、作成してください。
相関サブクエリを使用して実装:
SELECT run_id
, (SELECT frame
FROM run.frames_stat
WHERE run_id = r.run_id
ORDER BY frame DESC NULLS LAST
LIMIT 1) AS max_frame
, (SELECT "time"
FROM run.frames_stat
WHERE run_id = r.run_id
ORDER BY "time" DESC NULLS LAST
LIMIT 1) AS max_time
FROM run_tbl r;
2つの マルチカラムインデックス を作成し、 lightening パフォーマンスのソート順を一致させます。
CREATE index fun_frame_idx ON run.frames_stat (run_id, frame DESC NULLS LAST);
CREATE index fun_frame_idx ON run.frames_stat (run_id, "time" DESC NULLS LAST);
NULLS LAST
は、 can null値がある場合にのみ必要です。しかし、どちらにしても害はありません。
280種類のrun_id
のみで、 very 高速になります。
または、これらの重要な情報に基づいて:
「frames_stat」テーブルには4200万行あります
rows = 280-返される行の数=個別のrun_id
テーブルは不変です(挿入/削除なし)
MATERIALIZED VIEW
を使用すると、非常に小さく(280行のみ)、超高速になります。
テーブルではなく、MVに基づくようにクエリを変更する必要があります。
さておき: 予約語 (標準SQLでは)time
のように識別子として使用しないでください。
あなたはテーブルに_INSTEAD OF SELECT
_ルールを作成してみることができます..これはアプリケーションを壊すかもしれません(問題のテーブルを実際にすべて使用しているものに依存します)
_CREATE RULE "RETURN_MODIFIED_SELECT" AS
ON SELECT TO run.frames_stat
DO INSTEAD
<MY QUERY FROM BELOW>;
_
私は個人的にはRULE
sをあまり使用していません。そのため、これは完全に間違っている可能性があります。その場合は、誰かがコメントで修正してください。
ドキュメントからの引用:
現在、ON SELECTルールは無条件のINSTEADルールである必要があり、単一のSELECTコマンドで構成されるアクションが必要です。したがって、ON SELECTルールはテーブルをビューに効果的に変換します。その表示内容は、テーブルに格納されていたもの(存在する場合)ではなく、ルールのSELECTコマンドによって返された行です。実際のテーブルを作成してON SELECTルールを定義するよりも、CREATE VIEWコマンドを記述する方がスタイルが良いと考えられています。
下からのクエリが「単一の選択コマンド」と見なされない場合(CTEを使用していることを考えると、そうではない可能性があります)、クエリをカプセル化する関数を記述し、ルールが関数の選択を返すようにすることができます。
ORIGINAL POST BELOW-クエリを変更できないことがOPに追加されたので、以下は行われません。 OPのために働く(しかし、他の人がそれから利益を得る場合に備えて去る)
2つの個別のクエリに分割してみてください。
_WITH max_frame AS (
SELECT run_id, MAX(frame) AS max_frame FROM run.frames_stat GROUP BY run_id
), max_time AS (
SELECT run_id, MAX(time) AS max_time FROM run.frames_stat GROUP BY run_id
) SELECT
a.run_id, a.max_frame, b.max_time
FROM max_frame a
JOIN max_time b ON a.run_id = b.run_id
_
インデックスに関しては、run_idのインデックスで両方のクエリに十分である可能性がありますが、そうでない場合は、(run_id、frame)と(run_id、time)の2つのインデックスを試してください。
私はこれがクエリの改善に役立つと信じています-postgresオプティマイザはMAX (frame)
を見つける必要があるため、おそらくテーブルのほとんどの行をスキャンする必要があると想定しています(インデックスが使用可能であることがわかっていても)。 AND MAX (time)
を1回のパスで実行します。私が持っているように分割すると、(2つではなく)1つのMAX
値を見つけるだけでよいことがわかるため、インデックスを使用する必要があります。それを見つけるために。
そうでない場合は、@ a_horse_with_no_nameが提供したリンクでリクエストされたデータを提供する必要があります。