web-dev-qa-db-ja.com

大きなテーブルを使用した遅いウィンドウ関数クエリ

PostgreSQL 9.4rc1の新しいDB設計でパフォーマンステストを行っていますが、ウィンドウ関数を使用した非常に遅いクエリがいくつかあります。これが私のテーブル設定です:

CREATE TABLE player_stat (
  player_id    VARCHAR(200) NOT NULL,
  stat_id      BIGINT NOT NULL,
  value        BIGINT NOT NULL DEFAULT 0,
  last_updated TIMESTAMP WITH TIME ZONE NOT NULL,
  last_active  TIMESTAMP WITH TIME ZONE DEFAULT NULL,

  CONSTRAINT player_stat_pk PRIMARY KEY (player_id, stat_id),
  CONSTRAINT player_stat_fk1 FOREIGN KEY(stat_id) REFERENCES stat (id)
);
CREATE INDEX player_stat_stat_value_player_desc
  ON player_stat (stat_id, value DESC, player_id ASC);

3つの統計に分割されたこのテーブルに3000万行を挿入しました。

INSERT INTO player_stat (player_id, stat_id, value, last_updated) SELECT x.id, 1, x.v, now() FROM (SELECT generate_series(1,10000000) as id, trunc(random() * (1900-1200) + 1200) as v) AS x;
INSERT INTO player_stat (player_id, stat_id, value, last_updated) SELECT x.id, 2, x.v, now() FROM (SELECT generate_series(1,10000000) as id, trunc(random() * (1900-1200) + 1200) as v) AS x;
INSERT INTO player_stat (player_id, stat_id, value, last_updated) SELECT x.id, 3, x.v, now() FROM (SELECT generate_series(1,10000000) as id, trunc(random() * (1900-1200) + 1200) as v) AS x;

次に、指定された統計のプレーヤーをランク付けしようとします([〜#〜] edit [〜#〜]):

SELECT * FROM 
( SELECT player_id
       , rank() OVER (ORDER BY value DESC, player_id ASC)  as rank 
  FROM player_stat 
  WHERE stat_id = 1
) as t 
WHERE rank <= 20 
ORDER BY rank ASC;

このクエリが返されるまでに約5.5秒かかります。それに対してExplainを実行すると、以下が返されます。

"Sort  (cost=1167612.28..1176082.26 rows=3387993 width=15) (actual time=9726.132..9726.135 rows=20 loops=1)"
"  Sort Key: t.rank"
"  Sort Method: quicksort  Memory: 25kB"
"  ->  Subquery Scan on t  (cost=0.56..684349.57 rows=3387993 width=15) (actual time=0.080..9726.116 rows=20 loops=1)"
"        Filter: (t.rank <= 20)"
"        Rows Removed by Filter: 9999980"
"        ->  WindowAgg  (cost=0.56..557299.83 rows=10163979 width=15) (actual time=0.077..8351.124 rows=10000000 loops=1)"
"              ->  Index Only Scan using player_stat_stat_value_player_desc on player_stat  (cost=0.56..379430.20 rows=10163979 width=15) (actual time=0.054..2319.007 rows=10000000 loops=1)"
"                    Index Cond: (stat_id = 1)"
"                    Heap Fetches: 0"
"Planning time: 0.187 ms"
"Execution time: 9726.172 ms"

これをスピードアップする方法はありますか?テーブルにいるプレイヤーの数に比例して、時間がかかるようです。

5
Kyle

これをスピードアップする方法はありますか?

はい。varchar列にinteger番号を使用しないでください。多数のIDを書き込む場合は、integerまたはbigintを使用してください。テーブルとインデックスがはるかに小さく、処理が高速です。テストで1,000万行をランク付けしているので、これは大きな違いを生むことになります。

player_id VARCHAR(200) NOT NULL,
_player_id int NOT NULL,_

または、必要な場合はuuid(疑わしいです):

クエリは1000万行をランク付けします。インデックスから直接読み取られ、並べ替え手順がない場合でも、これにはしばらく時間がかかります。

注:最初に行を一括挿入し、その後にインデックスとPK制約(およびFK制約)を追加すると、muchより速く、さらにREINDEXまたは_VACUUM FULL_を実行しなくても、肥大化することなく完璧なインデックスを取得できます。ただし、パフォーマンスをテストする前に、ANALYZEがテーブルで実行されていることを確認してください。

あなたが尋ねなかったもの

..しかし、ここで外に出て、おそらく何を探しているのでしょう。

EXPLAIN出力は、上位20行をフィルタリングしたことを示しています:_(t.rank <= 20)_。 提示されたクエリはそれを示していませんEXPLAIN出力に実際に一致するクエリは次のようになります。

_SELECT * FROM (
   SELECT player_id
        , rank() OVER (ORDER BY value DESC, player_id ASC) AS rank
   FROM   player_stat
   WHERE  stat_id = 1
   ) t
WHERE t.rank <= 20;
_

どれを改善できるか劇的に

_SELECT row_number() OVER (ORDER BY value DESC, player_id ASC) AS rank
     , player_id
FROM   player_stat
WHERE  stat_id = 1
ORDER  BY value DESC, player_id
LIMIT  20;
_

説明

  • パフォーマンスにとって重要なのは、インデックスと一致する_ORDER BY_と組み合わせたLIMIT句です。これで、クエリは元のバージョンで10000000を読み取る必要があった上からインデックスまで正確に20行を読み取ります。 _player_id_とvalueのみを使用しているため、インデックスのみのスキャンを行うことができます。残りはピーナッツです。

  • SELECTクエリ内のイベントのシーケンス によるすべてです。ウィンドウ関数が適用されますbeforeLIMIT。ソート順が一致する場合のみ、適用可能な10000000行の残りを考慮する必要はありません。

  • 上位20のランクは20行を超えないことが保証されているため、_LIMIT 20_を使用できます。 _(player_id, stat_id)_のPKは、_player_id_ごとに一意の_stat_id_を保証します。これは_ORDER BY_に含まれているため、各ランクは1回だけ割り当てられます。 row_number() 代わりに。

6