postgresql COUNT (DISTINCT…) sehr langsam

166

Ich habe eine sehr einfache SQL-Abfrage:

SELECT COUNT(DISTINCT x) FROM table;

Meine Tabelle hat ungefähr 1,5 Millionen Zeilen. Diese Abfrage läuft ziemlich langsam. es dauert ungefähr 7,5s im Vergleich zu

 SELECT COUNT(x) FROM table;

das dauert etwa 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 für x zu erstellen. Beide haben die gleiche Ausführungszeit von 7,5 Sekunden.

ferson2020
quelle
Das glaube ich nicht. Das Erhalten der eindeutigen Werte von 1,5 Millionen Zeilen wird nur langsam sein.
Ry-
5
Ich habe es gerade in C # versucht. Das Abrufen der unterschiedlichen Werte von 1,5 Millionen Ganzzahlen aus dem Speicher dauert auf meinem Computer über eine Sekunde. Ich denke, Sie haben wahrscheinlich kein Glück.
Ry-
Der Abfrageplan hängt sehr stark von der Tabellenstruktur (Indizes) und der Einstellung der Optimierungskonstanten (Arbeit) (mem, effektive_Cache-Größe, zufällige_Seitenkosten) ab. Bei angemessener Abstimmung könnte die Abfrage möglicherweise in weniger als einer Sekunde ausgeführt werden.
Wildplasser
Könnten Sie genauer sein? Welche Indizes und Abstimmungskonstanten wären erforderlich, um sie in weniger als einer Sekunde zu erhalten? Nehmen wir der Einfachheit halber an, dass dies eine zweispaltige Tabelle mit einem Primärschlüssel in der ersten Spalte y ist, und ich führe diese 'eindeutige' Abfrage in einer zweiten Spalte x vom Typ int mit 1,5 Millionen Zeilen durch.
ferson2020
1
Bitte fügen Sie die Tabellendefinition allen Indizes bei (die \dAusgabe von psqlist gut) und geben Sie die Spalte an, mit der Sie Probleme haben. Es wäre gut, EXPLAIN ANALYZEbeide Fragen zu sehen.
Vyegorov

Antworten:

316

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)
Ankur
quelle
38
Heilige Fragen Batman! Dies beschleunigte meine Postgres-Zahl von 190 auf 4,5 whoa!
Rogerdpack
20
Ich habe diesen Thread auf www.postgresql.org gefunden , in dem es um dasselbe geht: Link . Eine der Antworten (von Jeff Janes) besagt, dass COUNT (DISTINCT ()) die Tabelle sortiert, um ihre Arbeit zu erledigen, anstatt Hash zu verwenden.
Ankur
5
@Ankur Darf ich dir eine Frage stellen? Da COUNT(DISTINCT())die Sortierung durchgeführt wird, ist es auf jeden Fall hilfreich, einen Index für die column_namebesonders kleine Menge von zu haben work_mem(wobei durch Hashing eine relativ große Menge an Chargen erzeugt wird). Seitdem ist es nicht immer schlecht, COUNT (DISTINCT () _ zu verwenden, nicht
wahr
2
@musmahn Count(column)zählt nur Nicht-Null-Werte. count(*)zählt Zeilen. Die erste / längere zählt also auch die Nullzeile (einmal). Wechseln Sie zu count(column_name), damit sie sich gleich verhalten.
GolezTrol
1
@ankur das war nicht sehr nützlich für mich .. habe keine bemerkenswerte Verbesserung bekommen.
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
        ;

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.

Wildplasser
quelle
2
Haben Sie den Effekt des Caching in Betracht gezogen? Wenn Sie anschließend drei "EXPLAIN-Analysen" durchführen, ruft die erste möglicherweise langsam Dinge von der Festplatte ab, während die beiden letzteren möglicherweise schnell aus dem Speicher abrufen.
Tobixen
In der Tat: effektiv_cache_size ist die erste Einstellung, die optimiert werden muss. Meins ist 2 GB, IIRC.
Wildplasser
Ich habe meine effektive_Cache-Größe auf 2 GB festgelegt, ohne die Leistung zu ändern. Gibt es noch andere Einstellungen, die Sie optimieren möchten? Wenn ja, zu was?
ferson2020
1) Wie hast du es eingestellt? (Hast du es HUP?) 2) Hast du tatsächlich so viel Speicher zur Verfügung? 3) Zeigen Sie uns Ihren Plan. 4) Vielleicht ist meine Maschine schneller oder Ihre hat mehr gleichzeitige Last zu bewältigen. @ ferson2020: Ok
Wildplasser
Ich setze es mit der Anweisung: SET effektiv_cache_size = '2GB'; Ich habe so viel Speicher zur Verfügung. Ich habe versucht, meinen Abfrageplan einzuschließen, aber er passt nicht in das Kommentarfeld.
ferson2020
2

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

Tometzky
quelle
0

Ich habe auch nach derselben Antwort gesucht, 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 Grenzwert / Versatz zu ermitteln. Normalerweise ist es schwierig, die Gesamtzahl mit Limit / Offset zu ermitteln. Endlich habe ich den Weg gefunden -

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

Die Abfrageleistung ist ebenfalls hoch.

Rana Pratap Singh
quelle