web-dev-qa-db-ja.com

MAXとgroupByを使用してPostgresにクエリのインデックスを作成する

次のクエリにインデックスを付ける方法はありますか?

SELECT run_id, MAX ( frame ) , MAX ( time ) FROM run.frames_stat GROUP BY run_id;

frametimeにソートされた(非複合)インデックス、およびrun_idにインデックスを作成しようとしましたが、クエリプランナーはそれらを使用しません。

その他の情報:

  • 残念ながら(そして私は入りません)クエリを変更できません
  • frames_statテーブルには4200万行あります
  • テーブルは変更されていません(これ以上の挿入/削除は行われません)
  • クエリは常に低速でしたが、このデータセットは以前よりも大きいため、遅くなっただけです。
  • テーブルにインデックスがありません
  • Postgres 9.4を使用しています
  • Dbの「work_mem」サイズは128MBです(それが適切な場合)。
  • ハードウェア:130GB RAM、10コアXeon

スキーマ:

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)
5
burnsy

残念な

クエリをまったく変更できない場合、それは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 高速になります。

マテリアライズドビュー

または、これらの重要な情報に基づいて:

  1. 「frames_stat」テーブルには4200万行あります

  2. rows = 280-返される行の数=個別のrun_id

  3. テーブルは不変です(挿入/削除なし)

MATERIALIZED VIEW を使用すると、非常に小さく(280行のみ)、超高速になります。
テーブルではなく、MVに基づくようにクエリを変更する必要があります。

さておき: 予約語 (標準SQLでは)timeのように識別子として使用しないでください。

8

あなたはテーブルに_INSTEAD OF SELECT_ルールを作成してみることができます..これはアプリケーションを壊すかもしれません(問題のテーブルを実際にすべて使用しているものに依存します)

_CREATE RULE "RETURN_MODIFIED_SELECT" AS
    ON SELECT TO run.frames_stat
    DO INSTEAD
        <MY QUERY FROM BELOW>;
_

私は個人的にはRULEsをあまり使用していません。そのため、これは完全に間違っている可能性があります。その場合は、誰かがコメントで修正してください。

ドキュメントからの引用:

現在、ON SELECTルールは無条件のINSTEADルールである必要があり、単一のSELECTコマンドで構成されるアクションが必要です。したがって、ON SELECTルールはテーブルをビューに効果的に変換します。その表示内容は、テーブルに格納されていたもの(存在する場合)ではなく、ルールのSELECTコマンドによって返された行です。実際のテーブルを作成してON SELECTルールを定義するよりも、CREATE VIEWコマンドを記述する方がスタイルが良いと考えられています。

下からのクエリが「単一の選択コマンド」と見なされない場合(CTEを使用していることを考えると、そうではない可能性があります)、クエリをカプセル化する関数を記述し、ルールが関数の選択を返すようにすることができます。

CREATE RULEドキュメント

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が提供したリンクでリクエストされたデータを提供する必要があります。

1
Joishi Bodio