2012-06-28 20 views
99

Mam bardzo proste zapytanie SQL:postgresql COUNT (DISTINCT ...) bardzo powolny

SELECT COUNT(DISTINCT x) FROM table; 

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

SELECT COUNT(x) FROM table; 

, która zajmuje około 435ms. Czy istnieje sposób na zmianę mojej zapytania w celu zwiększenia wydajności? Próbowałem już grupowania i regularnego liczenia, jak również umieszczania indeksu na x; oba mają taki sam czas wykonania 7,5s.

+0

Nie sądzę. Uzyskanie różnych wartości 1,5 miliona rzędów będzie po prostu powolne. – Ryan

+4

Po prostu próbowałem go w języku C#, otrzymując różne wartości 1,5 miliona * liczb całkowitych z pamięci * zajmuje ponad jedną sekundę na moim komputerze. Więc myślę, że prawdopodobnie masz pecha. – Ryan

+0

Plan zapytania będzie w dużym stopniu zależał od struktury tabeli (indeksów) i ustawień stałych strojenia (pracy), efektywnego rozmiaru_cache, losowego_page_cost). Przy rozsądnym strojeniu zapytanie może zostać wykonane w czasie krótszym niż sekunda. – wildplasser

Odpowiedz

8
-- 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 schemat jak dla CTE prawdopodobnie mógłby być także produkowany przez innych metod (funkcji okna)

+2

Czy brałeś pod uwagę efekt buforowania? Jeśli wykonanie trzech "wyjaśnić analizy" następnie, pierwszy może być powolne pobieranie rzeczy z dysku, podczas gdy dwa ostatnie mogą być szybkie pobieranie z pamięci. – tobixen

+0

Rzeczywiście: effect_cache_size jest pierwszym ustawieniem do dostrojenia. Mój jest 2GB, IIRC. – wildplasser

+0

Ustawiłem effective_cache_size na 2GB, bez żadnych zmian w wydajności. Jakieś inne ustawienia, które chcesz poprawić? Jeśli tak, do czego? – ferson2020

1

Jeśli Twój count(distinct(x)) jest znacznie wolniejszy niż count(x) następnie można przyspieszyć tę kwerendę, utrzymując liczbę wartości x w różnych tabelach, na przykład table_name_x_counts (x integer not null, x_count int not null), za pomocą wyzwalaczy. Ale wydajność zapisu będzie ucierpiała i jeśli zaktualizujesz wiele wartości x w pojedynczej transakcji, musisz to zrobić w jakiejś jawnej kolejności, aby uniknąć możliwego zakleszczenia.

201

Można to wykorzystać:

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

Jest to o wiele szybciej niż:

COUNT(DISTINCT column_name) 
+23

święte pytania batman! Przyspieszyło to moją liczbę postgresów od 190 do 4,5 whoa! – rogerdpack

+11

Chciałbym wyjaśnienie, dlaczego to działa. Świetna rada! – DavidMann10k

+11

Znalazłem ten wątek na [www.postgresql.org] (http://www.postgresql.org), który omawia to samo: [link] (http://www.postgresql.org/message-id/CAONnt+ [email protected]). Jedna z odpowiedzi (autorstwa Jeffa Janesa) mówi, że COUNT (DISTINCT()) sortuje tabelę, aby wykonać swoją pracę, zamiast używać skrótu. – Ankur