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"
これをスピードアップする方法はありますか?テーブルにいるプレイヤーの数に比例して、時間がかかるようです。
これをスピードアップする方法はありますか?
はい。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()
代わりに。