postgresql COUNT (DISTINCT…) bardzo wolno


166

Mam bardzo proste zapytanie SQL:

SELECT COUNT(DISTINCT x) FROM table;

Moja tabela ma około 1,5 miliona wierszy. To zapytanie działa dość wolno; zajmuje to około 7,5 s, w porównaniu do

 SELECT COUNT(x) FROM table;

co trwa około 435 ms. Czy istnieje sposób na zmianę zapytania w celu zwiększenia wydajności? Próbowałem grupować i regularnie liczyć, a także umieszczać indeks na x; oba mają ten sam czas wykonania 7,5 s.


Nie sądzę. Uzyskanie odrębnych wartości 1,5 miliona wierszy będzie po prostu powolne.
Ry-

5
Właśnie wypróbowałem to w C #, uzyskanie różnych wartości 1,5 miliona liczb całkowitych z pamięci zajmuje mi ponad sekundę. Więc myślę, że prawdopodobnie nie masz szczęścia.
Ry-

Plan zapytania będzie w dużej mierze zależał od struktury tabeli (indeksów) i ustawienia stałych strojenia (praca) mem, efektywny_konserwowany_koszt, losowy_koszt_strony). Przy rozsądnym dostrojeniu zapytanie mogłoby zostać wykonane w mniej niż sekundę.
wildplasser

Możesz być bardziej dokładny? Jakie indeksy i stałe dostrajania byłyby potrzebne, aby uzyskać czas poniżej sekundy? Dla uproszczenia załóżmy, że jest to tabela z dwiema kolumnami z kluczem podstawowym w pierwszej kolumnie y, a ja wykonuję to „odrębne” zapytanie na drugiej kolumnie x typu int, z 1,5 miliona wierszy.
ferson2020

1
Proszę dołączyć definicję tabeli do wszystkich indeksów ( \dwynik psqljest dobry) i sprecyzować kolumnę, z którą masz problem. Dobrze byłoby zobaczyć EXPLAIN ANALYZEoba zapytania.
vyegorov

Odpowiedzi:


316

Możesz użyć tego:

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

Jest to znacznie szybsze niż:

COUNT(DISTINCT column_name)

38
święte zapytania batmanie! To przyspieszyło moją liczbę postgresów z 190 do 4,5 whoa!
rogerdpack

20
Znalazłem ten wątek na www.postgresql.org, który omawia to samo: link . Jedna z odpowiedzi (autorstwa Jeffa Janesa) mówi, że COUNT (DISTINCT ()) sortuje tabelę, aby wykonać swoją pracę, zamiast używać skrótu.
Ankur,

5
@Ankur Czy mogę zadać pytanie? Ponieważ COUNT(DISTINCT())wykonuje sortowanie, z pewnością pomocne będzie posiadanie indeksu na column_nameszczególnie przy stosunkowo niewielkiej ilości work_mem(gdzie haszowanie da stosunkowo dużą liczbę partii). Od tego czasu użycie COUNT (DISTINCT () _, prawda?
St.Antario,

2
@musmahn Count(column)liczy tylko wartości inne niż null. count(*)liczy wiersze. Tak więc pierwszy / dłuższy zlicza również wiersz pusty (raz). Zmień na, count(column_name)aby zachowywały się tak samo.
GolezTrol

1
@ankur to nie było dla mnie zbyt przydatne… nie przyniosło żadnej znaczącej poprawy.
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
        ;

Wyniki:

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)

Ten sam plan, co w przypadku CTE, można by prawdopodobnie stworzyć innymi metodami (funkcje okna)


2
Czy zastanawiałeś się nad efektem buforowania? Jeśli wykonujesz następnie trzy "analizy wyjaśniające", pierwszy z nich może powoli pobierać rzeczy z dysku, podczas gdy dwa ostatnie mogą pobierać szybko z pamięci.
tobixen

Rzeczywiście: efektywne_cache_size to pierwsze ustawienie, które należy zmienić. Mój to 2 GB, IIRC.
wildplasser

Ustawiłem efektywny_cache_size na 2 GB bez zmiany wydajności. Jakieś inne ustawienia, które sugerowałbyś poprawienie? Jeśli tak, to czemu?
ferson2020

1) jak to ustawiłeś? (wyskoczyłeś?) 2) Czy faktycznie masz tyle dostępnej pamięci? 3) pokaż nam swój plan. 4) może moja maszyna jest szybsza lub Twoja ma do czynienia z większym obciążeniem równoległym. @ ferson2020: Ok
wildplasser

Ustawiłem to za pomocą instrukcji: SET Effective_cache_size = '2GB'; Mam tyle dostępnej pamięci. Próbowałem dołączyć mój plan zapytań, ale nie zmieścił się on w polu komentarza.
ferson2020

2

Jeśli twoja count(distinct(x))jest znacznie wolniejsza niż count(x)wtedy, możesz przyspieszyć to zapytanie, utrzymując liczniki wartości x w innej tabeli, na przykład table_name_x_counts (x integer not null, x_count int not null)używając wyzwalaczy. Jednak wydajność zapisu ucierpi i jeśli zaktualizujesz wiele xwartości w jednej transakcji, musisz to zrobić w jakiejś jawnej kolejności, aby uniknąć możliwego impasu.


0

Szukałem również tej samej odpowiedzi, ponieważ w pewnym momencie potrzebowałem total_count z odrębnymi wartościami wraz z limitem / przesunięciem .

Ponieważ jest to trochę trudne, aby uzyskać całkowitą liczbę z różnymi wartościami wraz z limitem / przesunięciem. Zwykle trudno jest uzyskać całkowitą liczbę z limitem / przesunięciem. W końcu udało mi się zrobić -

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

Wydajność zapytań jest również wysoka.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.