6

Ich habe eine Tabelle in Postgresql, die ein Array enthält, das ständig aktualisiert wird.Optimierung der Anzahl Abfrage für PostgreSQL

In meiner Anwendung muss ich die Anzahl der Zeilen erhalten, für die ein bestimmter Parameter in dieser Array-Spalte nicht vorhanden ist. Meine Abfrage sieht wie folgt aus:

select count(id) 
from table 
where not (ARRAY['parameter value'] <@ table.array_column) 

Aber wenn die Menge der Zeilen und die Anzahl der Ausführungen der Abfrage (mehrmals pro Sekunde, möglicherweise Hunderte oder Tausende) Erhöhen der Leistung decreses viel, so scheint es mir, dass Das Zählen in PostgreSQL könnte eine lineare Reihenfolge der Ausführung haben (ich bin mir da nicht ganz sicher).

Im Grunde ist meine Frage:

Gibt es ein bestehendes Muster, das ich davon auf diese Situation gilt nicht bewusst bin? Was wäre der beste Ansatz dafür?

Jeder Vorschlag, den Sie mir geben könnten, wäre sehr geschätzt.

+0

Nicht sicher, aber ich denke ein GIN-Index auf table.array_column wird helfen, dies zu beschleunigen. Sie müssen EXPLAIN ausführen, um das herauszufinden. Siehe hier: http://dba.stackexchange.com/a/27505/1822 –

+1

Es wird schwierig sein, dies effizient in Postgres zu machen, da der Tisch groß wird. Ein GIN-Index hilft nur beim Testen auf "enthalten in" im Gegensatz zu "nicht enthalten in" in Ihrem Prädikat. Wenn es nicht entscheidend ist, dass die Anzahl 100% genau ist, könnten Sie versuchen, sie mit einem TTL auf der App-Ebene zwischenzuspeichern. Wenn Ihre Schreibrate in der Tabelle nicht zu hoch ist, können Sie sinnvollerweise Trigger verwenden, um eine andere Tabelle mit aktuellen Zählwerten zu aktualisieren. – dbenhur

+0

Am Besten, um Ihre Version zu zeigen und 'explain analyze' zu ​​erklären; siehe http://stackoverflow.com/tags/postgresql-performance/info –

Antwort

2

Gibt es ein existierendes Muster, von dem ich nicht weiß, dass das auf diese Situation zutrifft? Was wäre der beste Ansatz dafür?

Ihre beste Wette in dieser Situation könnte sein, Ihr Schema zu normalisieren. Teilen Sie das Array in eine Tabelle auf. Fügen Sie der Eigenschaftentabelle einen b-tree-Index hinzu, oder ordnen Sie den Primärschlüssel so zu, dass er von property_id effizient durchsucht werden kann.

CREATE TABLE demo(id integer primary key); 
INSERT INTO demo (id) SELECT id FROM arrtable; 
CREATE TABLE properties (
    demo_id integer not null references demo(id), 
    property integer not null, 
    primary key (demo_id, property) 
); 
CREATE INDEX properties_property_idx ON properties(property); 

Sie dann die Eigenschaften abfragen:

SELECT count(id) 
FROM demo 
WHERE NOT EXISTS (
    SELECT 1 FROM properties WHERE demo.id = properties.demo_id AND property = 1 
) 

ich dies viel schneller als die ursprüngliche Abfrage zu erwarten, aber es ist eigentlich sehr ähnlich mit den gleichen Beispieldaten; Es läuft im selben 2s bis 3s Bereich wie Ihre ursprüngliche Abfrage. Es ist das gleiche Problem, wo Suche nach was ist nicht gibt es viel langsamer als die Suche nach was ist dort; Wenn wir nach Zeilen suchen, die eine Eigenschaft enthalten, können wir den Seqscan von demo vermeiden und einfach properties nach passenden IDs scannen.

Auch hier ist ein Seq-Scan auf der arrayhaltigen Tabelle genau das Richtige.

+0

danke für diese detaillierte Erklärung, ja aparently in meiner aktuellen Situation ist besser, die sequentielle Zählung zu tun oder denken Sie an eine andere Möglichkeit, die Informationen zu speichern, um die Suche schneller zu machen, wieder vielen Dank das war wirklich nützlich – jeruki

2

Ich denke mit Ihrem aktuellen Datenmodell Sie haben kein Glück. Denken Sie an einen Algorithmus, den die Datenbank für Ihre Abfrage ausführen muss. Ohne sequenzielles Scannen von Daten kann es nicht funktionieren.

Können Sie die Spalte so anordnen, dass sie die Umkehrung der Daten speichert (so dass die Abfrage select count(id) from table where ARRAY[‘parameter value’] <@ table.array_column wäre)? Diese Abfrage würde einen Gin/Gist-Index verwenden.

5

PostgreSQL unterstützt tatsächlich GIN-Indizes für Array-Spalten. Unglücklicherweise scheint es für NOT ARRAY[...] <@ indexed_col nicht verwendbar zu sein, und GIN Indizes sind ohnehin für häufig aktualisierte Tabellen ungeeignet.

Demo:

CREATE TABLE arrtable (id integer primary key, array_column integer[]); 

INSERT INTO arrtable(1, ARRAY[1,2,3,4]); 

CREATE INDEX arrtable_arraycolumn_gin_arr_idx 
ON arrtable USING GIN(array_column); 

-- Use the following *only* for testing whether Pg can use an index 
-- Do not use it in production. 
SET enable_seqscan = off; 

explain (buffers, analyze) select count(id) 
from arrtable 
where not (ARRAY[1] <@ arrtable.array_column); 

Leider ist dies zeigt, dass geschrieben wurden, können wir den Index nicht nutzen. Wenn Sie die Bedingung nicht negieren, kann sie verwendet werden, sodass Sie nach Zeilen suchen und diese zählen können, die do das Suchelement enthalten (durch Entfernen von NOT).

Sie können den Index verwenden, um Einträge zu zählen, die do den Zielwert enthalten, dann subtrahieren Sie dieses Ergebnis von einer Anzahl aller Einträge. Da count alle Zeilen in einer Tabelle in PostgreSQL (9.1 und älter) sehr langsam sind und einen sequenziellen Scan erfordern, ist dies tatsächlich langsamer als Ihre aktuelle Abfrage. Es ist möglich, dass auf 9.2 ein Index-Only-Scan verwendet werden kann, um die Zeilen zu zählen, wenn Sie einen B-Baum-Index auf id haben, in welchem ​​Fall der eigentlich in Ordnung sein könnte:

SELECT (
    SELECT count(id) FROM arrtable 
) - (
    SELECT count(id) FROM arrtable 
    WHERE (ARRAY[1] <@ arrtable.array_column) 
); 

Es garantiert werden schlechter abschneiden als Ihre Originalversion für Pg 9.1 und darunter, weil zusätzlich zu der Seqscan Ihr Original benötigt es auch braucht eine GIN-Index-Scan. Ich habe es jetzt auf 9.2 getestet und es scheint, dass es einen Index für die Zählung verwendet, also lohnt es sich, nach 9.2 zu suchen. Mit etwas weniger trivial Dummy-Daten:

drop index arrtable_arraycolumn_gin_arr_idx ; 
truncate table arrtable; 
insert into arrtable (id, array_column) 
select s, ARRAY[1,2,s,s*2,s*3,s/2,s/4] FROM generate_series(1,1000000) s; 
CREATE INDEX arrtable_arraycolumn_gin_arr_idx 
ON arrtable USING GIN(array_column); 

Beachten Sie, dass ein GIN Index wie dieser Updates verlangsamen wird viel nach unten, und ist ziemlich langsam in erster Linie zu erstellen. Es ist nicht geeignet für Tabellen, die viel aktualisiert werden - wie Ihr Tisch.

Schlimmer, Die Abfrage mit diesem Index dauert bis zu zweimal so lange wie Ihre ursprüngliche Abfrage und bestenfalls halb so lang auf demselben Datensatz. Es ist am schlimmsten für Fälle, in denen der Index nicht sehr selektiv ist wie ARRAY[1] - 4s vs 2s für die ursprüngliche Abfrage. Wenn der Index sehr selektiv ist (dh nicht viele Übereinstimmungen, wie ARRAY[199]), läuft er in etwa 1,2 Sekunden im Vergleich zu den 3s des Originals. Dieser Index ist für diese Abfrage einfach nicht wert.

Die Lektion hier? Manchmal ist die richtige Antwort nur ein sequentieller Scan.

Da dies für Ihre Trefferquoten nicht, entweder eine materialisierte Ansicht mit einem Trigger halten, wie @debenhur vermuten läßt, oder versuchen, das Array zu invertieren eine Liste von Parametern zu sein, dass der Eintrag nicht hat Du so kann einen GiST-Index verwenden, wie @maniek es vorschlägt.