it-swarm.com.de

PostgreSQL NOT IN Array langsame Abfrage

Ich habe eine große Tabelle mit Millionen von Zeilen. Jede Zeile hat ein Array-Feld tags. Ich habe auch den richtigen GIN-Index für tags.

Das Zählen der Zeilen mit einem Tag ist schnell (~ 7s):

SELECT COUNT(*) FROM "subscriptions" WHERE (tags @> ARRAY['t1']::varchar[]);

Das Zählen der Zeilen ohne Tag ist jedoch extrem langsam (~ 70s):

SELECT COUNT(*) FROM "subscriptions" WHERE NOT (tags @> ARRAY['t1']::varchar[]);

Ich habe auch andere Varianten ausprobiert, aber mit den gleichen Ergebnissen (~ 70er Jahre):

SELECT COUNT(*) FROM "subscriptions" WHERE NOT ('t1' = ANY (tags));

Wie kann ich den Vorgang "Nicht im Array" schnell ausführen?

3
collimarco

Ich habe danke an Jeff Janes auf der pgsql-Performance-Mailingliste gelöst:

Der GIN-Index wurde von PostgreSQL nicht für die Operation "NOT" verwendet. Das Erstellen eines Btree-Index für das gesamte Array löste das Problem und ermöglichte nur einen Index-Scan. Jetzt dauert die Abfrage nur noch wenige Millisekunden statt Minuten.

3
collimarco

Wenn 't1' ein seltenes Tag ist, führt das Zählen von Zeilen, die kein Tag haben, dazu, dass die meisten Ihrer "Millionen Zeilen" gezählt werden. Und selbst wenn 't1' sehr häufig vorkommt, ist das Zählen von mehr als ein paar Prozent der Zeilen aus dem Index keine Verbesserung gegenüber einem sequentiellen Scan. In jedem Fall wird dies niemals sehr schnell gehen. Indizes werden nicht helfen.

Wenn Sie mehrere Zählungen ohne seltene Tags durchführen müssen - und sich die Gesamtzahl der Zeilen in der Zwischenzeit nicht ändert (oder die minimale Änderung keine Rolle spielt), besteht eine mögliche Optimierung darin, die Gesamtzahl der Zeilen einmal (langsam) und zu ermitteln subtrahieren Sie die (kleine) Anzahl der Zeilen mit dem Tag (schnell mit übereinstimmendem Index) ...

Abhängig von den genauen Anforderungen und Ihrem vollständigen Anwendungsfall kann es andere Verknüpfungen geben. Sehen:

Unter dem Strich können Indizes normalerweise nur dazu beitragen, einen relativ kleinen Prozentsatz der Tabellenzeilen zu identifizieren. Übrigens, IN, = ANY() und die Containment-Operatoren @> sind verwandte Werkzeuge, aber mit subtilen Unterschieden. GIN-Indizes unterstützen normalerweise nur die richtigen Array-Operatoren. Sehen:

Die Verwendung von Integer-Arrays in Kombination mit Operatoren und einem Index, der auf einer Operatorklasse basiert, die vom zusätzlichen Modul intarray bereitgestellt wird, kann etwas bewirken. Hoch optimiert, aber es kann nicht über die Prinzipien hinaus trotzen.

Sie können dann auch any mixture of tags that the row must have or must not have wie du in einem query_int Ausdruck .

1

Sie haben bereits eine gute Antwort erhalten. Hier finden Sie weitere Informationen zum Nachdenken. Zunächst erinnerte mich Ihre Frage an eine interessant klingende Technik:

https://heap.io/blog/engineering/running-10-million-postgresql-indexes-in-production

Ich würde mich für Kommentare von denen interessieren, die eine solche Strategie ausprobiert haben.

Als weiterer Gedanke besteht eine andere Möglichkeit darin, eigene Häufigkeitstabellen für Tags und deren Auftreten zu verwalten. Das kann Ihnen Informationen geben, die Sie bei Ihrem eigenen Codegenerator unterstützen. Die Idee hier ist, dass der generische Abfrageplaner/-optimierer nie so viel über Ihre spezifischen Daten wie Sie. Mit Häufigkeitszählungen, selbst einigermaßen guten ungefähren Zählungen, können Sie verschiedene Abfragen erstellen, um sie für verschiedene Fälle an Postgres zu senden.

Diese Idee der Frequenzzählung ausarbeiten

Ich habe hier ein wenig näher darauf eingegangen, da meine ursprüngliche Kurzantwort nicht klar war. Die Idee hier ist, dass Sie eine Tabelle mit Frequenzzählungen wie tag_count mit eindeutigen Tags und einer Zählung. Mit diesen kleinen Daten können Sie testen, wie häufig Tags in einer Abfrage sind , bevor die eigentliche Abfrage für Postgres generiert wird. Dieser "einfache" Plan hängt von mehreren Dingen ab, von denen eine beliebige Anzahl in Ihrem Fall möglicherweise nicht zutrifft:

  • Sie haben Code, der die Abfragen erstellt, die für diesen Vorverarbeitungsschritt geändert werden können, um herauszufinden, wie die Abfrage am besten erstellt werden kann.

  • Sie können Wege finden, die Frequenzzählungen zu verwenden, um dem Planer zu helfen, eine bessere Arbeit zu leisten.

  • Es gibt eine Möglichkeit, den Aktualisierungscode für die Frequenzzählung auszuführen.

  • Es gibt eine Möglichkeit, die Anzahl mit angemessener Genauigkeit und ohne Blockierung des Systems aufrechtzuerhalten.

Dieser letzte Punkt ist offensichtlich ein großes Thema. Der einfachste Weg (konzeptionell) ist ein Auslöser für Hinzufügen/Ändern/Löschen, der die alten und neuen Tags findet und die Anzahl entsprechend anpasst. Nicht die leistungsstärkste Lösung und ein potenzieller Engpass. Es gibt viele, viele alternative Designs. (Ein Trigger auf Anweisungsebene mit einer Warteschlangentabelle nach dem Abgleichen wäre ein alternatives Design, das keinen Engpass darstellt.) Ehrlich gesagt kenne ich die Strategien mit der besten Leistung für inkrementelle Aktualisierungen in Postgres noch nicht. Ich habe vor ein paar Monaten ~ 10 Strategien für mich entworfen, bin aber nicht zum Testen und Vergleichen von Lösungen zurückgekehrt. Andere Leute in diesem Forum verwenden Postgres seit Ewigkeiten und sind super schlau und hilfreich. Wenn Sie also nach einer solchen Lösung suchen, sollten Sie erneut nachfragen.

1
Morris de Oryx