postgresql COUNT(DISTINCT…)非常に遅い


166

私は非常に単純なSQLクエリを持っています:

SELECT COUNT(DISTINCT x) FROM table;

テーブルには約150万行あります。このクエリの実行速度はかなり遅いです。と比較すると、約7.5秒かかります

 SELECT COUNT(x) FROM table;

約435msかかります。クエリを変更してパフォーマンスを向上させる方法はありますか?私はグループ化して定期的なカウントを試み、xにインデックスを付けました。どちらも7.5秒の実行時間は同じです。


そうは思いません。150万行の個別の値を取得するのは、遅いだけです。
Ry-

5
C#で試してみたところ、メモリから 150万の整数の個別の値を取得するのに、私のコンピューターでは1秒以上かかりました。だからあなたはおそらく運が悪いと思います。
Ry-

クエリプランは、テーブル構造(インデックス)とチューニング定数(work)mem、effective_cache_size、random_page_cost)の設定に大きく依存します。適切な調整を行うと、クエリが1秒未満で実行される可能性があります。
wildplasser

もっと具体的に教えていただけますか?1秒未満で取得するには、どのインデックスと定数を調整する必要がありますか?簡単にするために、これが最初の列yに主キーを持つ2列のテーブルであり、150万行のint型の2番目の列xでこの「個別の」クエリを実行するとします。
ferson2020 2012年

1
すべてのインデックスを含むテーブル定義(\d出力psqlは良いもの)を含め、問題のある列を正確に入力してください。EXPLAIN ANALYZE両方のクエリを確認するとよいでしょう。
vyegorov 2012年

回答:


316

あなたはこれを使うことができます:

SELECT COUNT(*) FROM (SELECT DISTINCT column_name FROM table_name) AS temp;

これは以下よりもはるかに高速です。

COUNT(DISTINCT column_name)

38
神聖なバットマンをクエリ!これにより、postgresの数が190から4.5に急増しました。
rogerdpack 2014年

20
私はwww.postgresql.orgでこのスレッドを見つけましたが、同じことについて説明しています:link。返信の1つ(Jeff Janesによる)は、COUNT(DISTINCT())がテーブルをソートして、ハッシュを使用する代わりに作業を行うと述べています。
Ankur 14

5
@Ankur質問してもいいですか?COUNT(DISTINCT())ソートを実行するので、column_name特にwork_memハッシュが比較的大量のバッチを生成する場合は、比較的少量のインデックスを持つと間違いなく役立ちます。それ以来、COUNT(DISTINCT()_を使用することは必ずしも悪いことではありませんか?
St.Antario

2
@musmahn Count(column)はnull以外の値のみをカウントします。count(*)行をカウントします。したがって、最初の/より長いものも、null行を(1回だけ)カウントします。に変更しcount(column_name)て、それらが同じように動作するようにします。
GolezTrol

1
@ankurこれは私にはあまり役に立ちませんでした。驚くべき改善は得られませんでした。
Shiwangini、

11
-- My default settings (this is basically a single-session machine, so work_mem is pretty high)
SET effective_cache_size='2048MB';
SET work_mem='16MB';

\echo original
EXPLAIN ANALYZE
SELECT
        COUNT (distinct val) as aantal
FROM one
        ;

\echo group by+count(*)
EXPLAIN ANALYZE
SELECT
        distinct val
       -- , COUNT(*)
FROM one
GROUP BY val;

\echo with CTE
EXPLAIN ANALYZE
WITH agg AS (
    SELECT distinct val
    FROM one
    GROUP BY val
    )
SELECT COUNT (*) as aantal
FROM agg
        ;

結果:

original                                                      QUERY PLAN                                                      
----------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=36448.06..36448.07 rows=1 width=4) (actual time=1766.472..1766.472 rows=1 loops=1)
   ->  Seq Scan on one  (cost=0.00..32698.45 rows=1499845 width=4) (actual time=31.371..185.914 rows=1499845 loops=1)
 Total runtime: 1766.642 ms
(3 rows)

group by+count(*)
                                                         QUERY PLAN                                                         
----------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=36464.31..36477.31 rows=1300 width=4) (actual time=412.470..412.598 rows=1300 loops=1)
   ->  HashAggregate  (cost=36448.06..36461.06 rows=1300 width=4) (actual time=412.066..412.203 rows=1300 loops=1)
         ->  Seq Scan on one  (cost=0.00..32698.45 rows=1499845 width=4) (actual time=26.134..166.846 rows=1499845 loops=1)
 Total runtime: 412.686 ms
(4 rows)

with CTE
                                                             QUERY PLAN                                                             
------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=36506.56..36506.57 rows=1 width=0) (actual time=408.239..408.239 rows=1 loops=1)
   CTE agg
     ->  HashAggregate  (cost=36464.31..36477.31 rows=1300 width=4) (actual time=407.704..407.847 rows=1300 loops=1)
           ->  HashAggregate  (cost=36448.06..36461.06 rows=1300 width=4) (actual time=407.320..407.467 rows=1300 loops=1)
                 ->  Seq Scan on one  (cost=0.00..32698.45 rows=1499845 width=4) (actual time=24.321..165.256 rows=1499845 loops=1)
       ->  CTE Scan on agg  (cost=0.00..26.00 rows=1300 width=0) (actual time=407.707..408.154 rows=1300 loops=1)
     Total runtime: 408.300 ms
    (7 rows)

CTEと同じ計画は、おそらく他の方法(ウィンドウ関数)でも作成できます。


2
キャッシュの影響を考慮しましたか?続いて3つの「説明分析」を行う場合、最初の1つはディスクからのフェッチに時間がかかり、後者の2つはメモリからのフェッチに時間がかかる場合があります。
tobixen

実際、effective_cache_sizeは最初に調整する設定です。鉱山は2GB、IIRCです。
wildplasser

パフォーマンスに変更を加えずに、effective_cache_sizeを2GBに設定しました。調整を提案する他の設定はありますか?もしそうなら、何に?
ferson2020

1)どのように設定しましたか?(HUPしましたか?)2)実際にそれだけ多くのメモリを利用できますか?3)あなたの計画を見せてください。4)多分私のマシンの方が速いか、あなたのマシンはより多くの同時負荷を処理する必要があります。@ ferson2020:Ok
wildplasser 2012年

次のステートメントを使用して設定します。SET effective_cache_size = '2GB'; 十分なメモリを利用できます。クエリプランを含めてみましたが、コメントボックスに収まりません。
ferson2020 2012年

2

count(distinct(x))が大幅に遅い場合は、トリガーを使用するcount(x)などしてtable_name_x_counts (x integer not null, x_count int not null)、異なるテーブルでx値のカウントを維持することにより、このクエリを高速化できます。ただし、書き込みパフォーマンスが低下しx、単一のトランザクションで複数の値を更新する場合は、デッドロックを回避するために、これを何らかの明示的な順序で実行する必要があります。


0

私も同じ答えを探していました。ある時点で、limit / offsetとともに個別の値を持つtotal_countが必要になったためです。

それを行うのは少し難しいので、制限/オフセットと一緒に個別の値で合計数を取得します。通常、制限/オフセットで合計数を取得することは困難です。最後に、私はやる方法を得ました-

SELECT DISTINCT COUNT(*) OVER() as total_count, * FROM table_name limit 2 offset 0;

クエリのパフォーマンスも高いです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.