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.