it-swarm.com.de

postgresql COUNT (DISTINCT ...) sehr langsam

Ich habe eine sehr einfache SQL-Abfrage:

SELECT COUNT(DISTINCT x) FROM table;

Mein Tisch hat ungefähr 1,5 Millionen Zeilen. Diese Abfrage wird ziemlich langsam ausgeführt. es dauert ungefähr 7.5s, verglichen mit

 SELECT COUNT(x) FROM table;

das dauert ungefähr 435ms. Gibt es eine Möglichkeit, meine Abfrage zu ändern, um die Leistung zu verbessern? Ich habe versucht, zu gruppieren und regelmäßig zu zählen sowie einen Index auf x zu setzen. beide haben dieselbe Ausführungszeit von 7,5 s.

118
ferson2020

Sie können dies verwenden:

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

Das ist viel schneller als:

COUNT(DISTINCT column_name)
241
Ankur
-- 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
        ;

Ergebnisse:

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)

Der gleiche Plan wie für den CTE könnte wahrscheinlich auch mit anderen Methoden (Fensterfunktionen) erstellt werden.

10
wildplasser

Wenn Ihre count(distinct(x)) wesentlich langsamer ist als count(x), können Sie diese Abfrage beschleunigen, indem Sie die Anzahl der x-Werte in verschiedenen Tabellen (z. B. table_name_x_counts (x integer not null, x_count int not null)) mithilfe von Triggern speichern. Ihre Schreibleistung leidet jedoch darunter, und wenn Sie mehrere x-Werte in einer einzigen Transaktion aktualisieren, müssen Sie dies in einer expliziten Reihenfolge tun, um einen möglichen Deadlock zu vermeiden.

1
Tometzky

Ich suchte auch nach der gleichen Antwort, weil ich irgendwann total_count mit unterschiedlichen Werten zusammen mit limit/offset brauchte.

Weil es wenig schwierig ist, die Gesamtzahl mit unterschiedlichen Werten zusammen mit dem Limit/Offset zu ermitteln. Normalerweise ist es schwierig, die Gesamtzahl mit Limit/Offset zu ermitteln. Endlich habe ich den Weg zu tun -

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

Die Abfrageleistung ist ebenfalls hoch.

0