PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Optimierung der Zählabfrage für PostgreSQL

PostgreSQL unterstützt tatsächlich GIN-Indizes für Array-Spalten. Leider 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 zeigt dies, dass wir den Index wie geschrieben nicht verwenden können. Wenn Sie die Bedingung nicht negieren, kann sie verwendet werden, sodass Sie Zeilen suchen und zählen können, die tun das Suchelement enthalten (durch Entfernen von NOT ).

Sie könnten den Index verwenden, um Einträge zu zählen, die tun den Zielwert enthalten, dann subtrahieren Sie dieses Ergebnis von einer Zählung aller Einträge. Seit count Das Überprüfen aller Zeilen in einer Tabelle ist in PostgreSQL (9.1 und älter) ziemlich langsam und erfordert einen sequentiellen Scan, der tatsächlich langsamer ist als Ihre aktuelle Abfrage. Es ist möglich, dass unter 9.2 ein Nur-Index-Scan verwendet werden kann, um die Zeilen zu zählen, wenn Sie einen B-Tree-Index für id haben , in diesem Fall könnte dies tatsächlich in Ordnung sein:

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

Es ist garantiert schlechter als Ihre Originalversion für Pg 9.1 und niedriger, da Ihr Original es zusätzlich zum Seqscan auch benötigt benötigt einen GIN-Index-Scan. Ich habe dies jetzt auf 9.2 getestet und es scheint einen Index für die Zählung zu verwenden, also lohnt es sich, nach 9.2 zu suchen. Mit einigen weniger trivialen 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 die Aktualisierungen SEHR verlangsamt und von vornherein ziemlich langsam zu erstellen ist. Es ist nicht geeignet für Tabellen, die häufig aktualisiert werden - wie Ihre Tabelle.

Schlimmer noch, die Abfrage, die diesen Index verwendet, dauert doppelt so lange wie Ihre ursprüngliche Abfrage und bestenfalls halb so lange auf demselben Datensatz. Am schlimmsten ist es in Fällen, in denen der Index nicht sehr selektiv ist, wie ARRAY[1] - 4s vs. 2s für die ursprüngliche Abfrage. Wo der Index sehr selektiv ist (dh:nicht viele Übereinstimmungen, wie ARRAY[199] ) läuft es in etwa 1,2 Sekunden gegenüber den 3 Sekunden des Originals. Dieser Index lohnt sich für diese Abfrage einfach nicht.

Die Lektion hier? Manchmal ist die richtige Antwort einfach ein sequenzieller Scan.

Da dies für Ihre Trefferquoten nicht ausreicht, behalten Sie entweder eine materialisierte Ansicht mit einem Trigger bei, wie @debenhur vorschlägt, oder versuchen Sie, das Array so umzukehren, dass es eine Liste von Parametern ist, die der Eintrag nicht enthält haben, damit Sie einen GiST-Index verwenden können, wie @maniek vorschlägt.