個別選択を高速化する方法は?


16

私はいくつかの時系列データで単純な選択を区別しています:

SELECT DISTINCT user_id
FROM events
WHERE project_id = 6
AND time > '2015-01-11 8:00:00'
AND time < '2015-02-10 8:00:00';

そして、それは112秒かかります。クエリプランは次のとおりです。

http://explain.depesz.com/s/NTyA

私のアプリケーションは、多くの異なる操作を実行する必要があり、このようにカウントします。この種のデータを取得するより速い方法はありますか?

回答:


19

おそらくこれを聞きたくないでしょうが、スピードアップSELECT DISTINCTするための 最良の選択肢はDISTINCT、最初から避けることです。多くの場合(すべてではありません!)、より良いデータベース設計またはより良いクエリで回避できます。

場合によってGROUP BYは、異なるコードパスを使用するため、高速になります。

では、あなたの特定のケースあなたは取り除くことができますように、それはいないようですDISTINCT。ただし、そのようなクエリが多数ある場合は、特殊なインデックスでクエリをサポートできます。

CREATE INDEX foo ON events (project_id, "time", user_id);

追加user_idは、これからインデックスのみのスキャンを取得する場合にのみ役立ちます。詳細については、リンクをたどってください。クエリプランの90%を消費する高価なビットマップヒープスキャンをクエリプランから削除します。

あなたのEXPLAIN出力は、クエリは、五十万一致する行のうち、2491人の異なるユーザーを凝縮していることを私に伝えます。あなたが何をしても、これは超高速になりませんが、かなり速くなります。

クエリの時間間隔は常に同じであれば、MATERIALIIZED VIEW折りたたみuser_idあたりは(project_id, <fixed time intervall>)長い道のりを行くだろう。ただし、さまざまな時間間隔でチャンスはありません。たぶん、ユーザーを少なくとも1時間あたりまたは他の最小時間単位でフォールドできれば、かなりのオーバーヘッドを保証するのに十分なパフォーマンスが得られます。

Nitpick:
おそらく、上の述語"time"は実際にあるはずです:

AND "time" >= '2015-01-11 8:00:00'
AND "time" <  '2015-02-10 8:00:00';

余談:識別子として
使用しないでくださいtime。これは標準SQLの予約語であり、Postgresの基本型です。


インデックスのみのスキャンについて少し読んだことがあるので、試してみましょう。
サム

残念ながら、時間間隔は固定されていません。
サム

@Sam:それで、提案されたインデックスを使用して、クエリの例はどれくらい速くなりましたか?
アーウィンブランドステッター

3
@edwin:まだプロダクションを試していません。ただし、ローカルで(同じデータを使用して)元のクエリを実行すると、3678.780ミリ秒かかりました。次に、インデックスを追加し、170.156ミリ秒まで高速化しました。プランに「イベントでfooを使用したインデックスのみスキャン」が含まれるようになりました。
サム

1
@サム:いいね!それが私が目指していたものです。
アーウィンブランドステッター

2

サムのケースとアーウィンの答えに対する私のテストです

drop table t1
create table t1 (id int, user_id int, project_id int, date_time timestamp without time zone) ;

insert into t1 -- 10 million row - size="498 MB"
select row_number() over(), round(row_number() over()/1000), round(row_number() over()/100000) , date
from generate_series('2015-01-01'::date, '2016-12-01'::date,'6 seconds'::interval
) date 
limit 10000000

-- before indexing - 10000000 row - output=100 row - time=2900ms
SELECT DISTINCT user_id
FROM t1
WHERE project_id = 1
AND date_time > '2015-01-01 8:00:00'
AND date_time < '2016-12-01 8:00:00' ;

CREATE INDEX foo ON t1 (project_id, date_time, user_id); -- time process=51.2 secs -- size="387 MB"         

-- after indexing - 10000000 row - output=100 row - time= 75ms (reduce ~ 38 times)
SELECT DISTINCT user_id
FROM t1
WHERE project_id = 1
AND date_time > '2015-01-01 00:00:00'
AND date_time < '2016-12-01 00:00:00' ;

アーウィンは、「おそらくこれを聞きたくないでしょうが、SELECT DISTINCTを高速化するための最良の選択肢は、DISTINCTを避けることです。多くの場合(すべてではありません!)、より良いデータベース設計またはより良いクエリで回避できます」私は彼が正しいと思う、「明確な、グループ化、順序付け」(もしあれば)の使用を避けるべきだ

サムの場合のような状況に遭遇し、サムは月ごとにイベントテーブルのパーティションを使用できると思います。クエリを実行するとデータサイズが小さくなりますが、上記のクエリの代わりに関数(pl / pgsql)を実行する必要があります。この関数は、queryを実行するための適切なパーティション(条件に依存)を見つけます。


2
>彼は正しいと思います。 "distinct、group by、order by"の使用は避け、SELECT、INSERT、UPDATEも使用しないでください。これらの構造を避けると、データベースは非常に高速になります!
-greatvovan
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.