postgresql COUNT (DISTINCT…) çok yavaş


166

Çok basit bir SQL sorgusu var:

SELECT COUNT(DISTINCT x) FROM table;

Masamın yaklaşık 1,5 milyon satırı var. Bu sorgu oldukça yavaş çalışıyor; ile karşılaştırıldığında yaklaşık 7.5s sürer

 SELECT COUNT(x) FROM table;

yaklaşık 435ms sürer. Performansı artırmak için sorgumu değiştirmenin bir yolu var mı? Gruplama ve düzenli bir sayım yapmanın yanı sıra x üzerinde bir dizin koymayı denedim; her ikisi de aynı 7.5s yürütme süresine sahiptir.


Ben öyle düşünmüyorum. 1,5 milyon satırlık farklı değerleri elde etmek yavaş olacaktır.
Ry-

5
Ben sadece C # denedim, 1,5 milyon tamsayı bellekten farklı değerleri bilgisayarımda bir saniye sürer. Sanırım muhtemelen şansın yok.
Ry-

Sorgu planı büyük ölçüde tablo yapısına (dizinler) ve ayarlama sabitlerinin (iş) mem, efektif_önbellek_boyutu, rastgele_sayfa_kost) ayarına bağlı olacaktır. Makul ayarlama ile sorgu bir saniyeden daha kısa bir sürede gerçekleştirilebilir.
wildplasser

Daha spesifik olabilir misiniz? Bir saniyenin altına inmek için hangi indeksler ve ayar sabitleri gerekir? Basitlik için, bunun ilk sütunda y birincil anahtarı olan iki sütunlu bir tablo olduğunu varsayalım ve bu 'farklı' sorguyu, int türündeki x ikinci sütununda 1,5 milyon satırla yapıyorum.
ferson2020

1
Lütfen, tablo tanımını tüm dizinlerle ( \dçıktısı psqliyidir) ekleyin ve sorun yaşadığınız sütunu kesin olarak ekleyin . Her EXPLAIN ANALYZEiki sorguyu da görmek güzel olurdu .
vyegorov

Yanıtlar:


316

Bunu kullanabilirsiniz:

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

Bu çok daha hızlıdır:

COUNT(DISTINCT column_name)

38
kutsal sorgular batman! Bu 190g dan 4.5 whoa farklı postgres sayımı hızlandırdı!
rogerdpack

20
Bu konuyu aynı şeyi tartışan www.postgresql.org adresinde buldum : link . Yanıtlardan biri (Jeff Janes tarafından), COUNT (DISTINCT ()) öğesinin karma kullanmak yerine işini yapmak için sıraladığını söylüyor.
Ankur

5
@Ankur Size soru sorabilir miyim? Yana COUNT(DISTINCT())gerçekleştirdiği sıralama, bunun üzerinde bir dizin olması kesinlikle yararlı olacaktır column_namenispeten küçük miktarda özellikle work_mem(karma serilerin relatevely büyük miktarda üretecektir). O zamandan beri, COUNT (DISTINCT () _ kullanmak her zaman kötü değildir, değil mi?
St.Antario

2
@musmahn Count(column)yalnızca boş olmayan değerleri sayar. count(*)satırları sayar. İlk / daha uzun olanı da boş satırı (bir kez) sayar. count(column_name)Bunların aynı şekilde davranmasını sağlamak için değiştirin .
GolezTrol

1
@ankur bu benim için çok kullanışlı değildi. kayda değer bir gelişme olmadı.
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
        ;

Sonuçlar:

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 ile aynı plan muhtemelen diğer yöntemlerle de üretilebilir (pencere fonksiyonları)


2
Önbelleğe almanın etkisini düşündünüz mü? Daha sonra üç "açıkla analiz" yapılıyorsa, birincisi diskten yavaşça şeyler alırken, ikincisi bellekten hızlı bir şekilde alınabilir.
tobixen

Gerçekten de: active_cache_size ilk ayarlanan ayardır. Benimki 2GB, IIRC.
wildplasser

Effect_cache_size'mi performansta hiçbir değişiklik olmadan 2GB'a ayarladım. Ayarlamayı önereceğiniz başka ayarlar var mı? Eğer öyleyse, neye?
ferson2020

1) nasıl ayarladınız? (HUP bunu yaptın mı?) 2) Bu kadar belleğin var mı? 3) planınızı bize gösterin. 4) belki benim makine daha hızlı, ya da seninle başa çıkmak için daha eşzamanlı yük vardır. @ ferson2020: Tamam
wildplasser

Ben şu ifadeyle ayarladım: SET effect_cache_size = '2GB'; O kadar çok hafızam var. Sorgu planımı dahil etmeyi denedim, ancak yorum kutusuna sığmayacak.
ferson2020

2

Eğer count(distinct(x))değeriniz önemli ölçüde count(x)yavaşsa, farklı tablolarda x değeri sayılarını koruyarak, örneğin table_name_x_counts (x integer not null, x_count int not null)tetikleyicileri kullanarak bu sorguyu hızlandırabilirsiniz . Ancak yazma performansınız düşecektir ve xtek bir işlemde birden çok değeri güncellerseniz , olası kilitlenmeleri önlemek için bunu açık bir sırayla yapmanız gerekir.


0

Ben de aynı cevabı araştırıyordum, çünkü bir noktada limit / ofset ile birlikte farklı değerlerle total_count'a ihtiyacım vardı .

Çünkü bunu yapmak biraz zor- Sınır / ofset ile birlikte farklı değerlerle toplam sayım elde etmek. Genellikle limit / ofset ile toplam sayımı elde etmek zordur. Sonunda bunu yapmanın yolunu buldum -

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

Sorgu performansı da yüksektir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.