Wie kann man select distinct beschleunigen?

16

Ich habe bei einigen Zeitreihendaten eine einfache Auswahl getroffen:

SELECT DISTINCT user_id
FROM events
WHERE project_id = 6
AND time > '2015-01-11 8:00:00'
AND time < '2015-02-10 8:00:00';

Und es dauert 112 Sekunden. Hier ist der Abfrageplan:

http://explain.depesz.com/s/NTyA

Meine Anwendung muss viele verschiedene Operationen ausführen und zählt so. Gibt es einen schnelleren Weg, um diese Art von Daten zu erhalten?

Sam
quelle

Antworten:

19

Sie möchten dies wahrscheinlich nicht hören, aber die beste Möglichkeit, die Geschwindigkeit zu erhöhen, SELECT DISTINCTbesteht darin, zunächst zu vermeiden DISTINCT . In vielen Fällen (nicht allen!) Kann dies durch ein besseres Datenbank-Design oder bessere Abfragen vermieden werden.

Manchmal GROUP BYist es schneller, weil es einen anderen Codepfad benötigt.

In Ihrem speziellen Fall scheint es nicht so, als könnten Sie es loswerden DISTINCT. Sie können die Abfrage jedoch mit einem speziellen Index unterstützen, wenn Sie viele Abfragen dieser Art haben:

CREATE INDEX foo ON events (project_id, "time", user_id);

Das Hinzufügen user_idist nur dann sinnvoll, wenn Sie nur Index-Scans erhalten . Folgen Sie dem Link für Details. Entfernt den teuren Bitmap-Heap-Scan aus Ihrem Abfrageplan, der 90% der Abfragezeit beansprucht.

Ihre EXPLAINAusgabe sagt mir, dass die Abfrage 2.491 verschiedene Benutzer aus einer halben Million übereinstimmender Zeilen verdichten muss. Dies wird nicht superschnell, egal was Sie tun, aber es kann wesentlich schneller sein.

Wenn die Zeitintervalle in Ihren Abfragen immer gleich sind, würde eine MATERIALIIZED VIEWFaltung user_idpro (project_id, <fixed time intervall>)viel bewirken. Keine Chance da mit unterschiedlichen Zeitintervallen. Möglicherweise könnten Sie die Anzahl der Benutzer pro Stunde oder eine andere Mindestzeiteinheit verringern, und dies würde genügend Leistung erbringen, um den erheblichen Mehraufwand zu rechtfertigen.

Nitpick:
Höchstwahrscheinlich sollten die Prädikate "time"wirklich sein:

AND "time" >= '2015-01-11 8:00:00'
AND "time" <  '2015-02-10 8:00:00';

Nebenbei:
Nicht timeals Bezeichner verwenden. Es ist ein reserviertes Wort in Standard-SQL und ein Basistyp in Postgres.

Erwin Brandstetter
quelle
Ich habe ein bisschen über Index-Only-Scans gelesen und werde es versuchen.
Sam,
Leider ist das Zeitintervall nicht festgelegt.
Sam,
@Sam: Wie viel schneller wurde Ihre Beispielabfrage mit dem vorgeschlagenen Index?
Erwin Brandstetter
3
@edwin: Ich habe es noch nicht mit der Produktion versucht. Ich habe jedoch die ursprüngliche Abfrage auf meinem lokalen Computer (mit denselben Daten) ausgeführt und es dauerte 3678.780 ms. Dann fügte ich den Index hinzu und beschleunigte ihn auf 170.156 ms. Plan enthält jetzt "Nur Index-Scan mit foo on events".
Sam
1
@Sam: Schön! Das ist, was ich angestrebt habe.
Erwin Brandstetter
2

Hier ist mein Test zu Sams Fall und Erwins Antwort

drop table t1
create table t1 (id int, user_id int, project_id int, date_time timestamp without time zone) ;

insert into t1 -- 10 million row - size="498 MB"
select row_number() over(), round(row_number() over()/1000), round(row_number() over()/100000) , date
from generate_series('2015-01-01'::date, '2016-12-01'::date,'6 seconds'::interval
) date 
limit 10000000

-- before indexing - 10000000 row - output=100 row - time=2900ms
SELECT DISTINCT user_id
FROM t1
WHERE project_id = 1
AND date_time > '2015-01-01 8:00:00'
AND date_time < '2016-12-01 8:00:00' ;

CREATE INDEX foo ON t1 (project_id, date_time, user_id); -- time process=51.2 secs -- size="387 MB"         

-- after indexing - 10000000 row - output=100 row - time= 75ms (reduce ~ 38 times)
SELECT DISTINCT user_id
FROM t1
WHERE project_id = 1
AND date_time > '2015-01-01 00:00:00'
AND date_time < '2016-12-01 00:00:00' ;

Erwin sagte: "Das wollen Sie wahrscheinlich nicht hören, aber die beste Möglichkeit, SELECT DISTINCT zu beschleunigen, besteht darin, zunächst DISTINCT zu vermeiden. In vielen Fällen (nicht in allen Fällen!) Kann dies durch ein besseres Datenbankdesign oder bessere Abfragen vermieden werden ". Ich denke, er hat Recht, wir sollten vermeiden, "getrennt, gruppiert nach, geordnet nach" (falls vorhanden) zu verwenden.

Ich habe eine Situation wie in Sams Fall erlebt und ich denke, Sam kann die Partition für die Ereignistabelle monatlich verwenden. Es wird Ihre Datengröße reduzieren, wenn Sie abfragen, aber Sie benötigen eine Funktion (pl / pgsql), die anstelle der obigen Abfrage ausgeführt werden soll. Die Funktion findet geeignete Partitionen (abhängig von den Bedingungen), um die Abfrage auszuführen.

Luan Huynh
quelle
2
> Ich denke, er hat Recht, wir sollten vermeiden, "getrennt, gruppiert, sortiert nach" zu verwenden - und auch SELECT, INSERT und UPDATE. Wenn wir diese Konstrukte vermeiden, wird unsere Datenbank sehr schnell sein!
Greatvovan