Ich habe eine relativ einfache Abfrage für eine Tabelle mit 1,5 Millionen Zeilen:
SELECT mtid FROM publication
WHERE mtid IN (9762715) OR last_modifier=21321
LIMIT 5000;
EXPLAIN ANALYZE
Ausgabe:
Limit (cost=8.84..12.86 rows=1 width=8) (actual time=0.985..0.986 rows=1 loops=1) -> Bitmap Heap Scan on publication (cost=8.84..12.86 rows=1 width=8) (actual time=0.984..0.985 rows=1 loops=1) Recheck Cond: ((mtid = 9762715) OR (last_modifier = 21321)) -> BitmapOr (cost=8.84..8.84 rows=1 width=0) (actual time=0.971..0.971 rows=0 loops=1) -> Bitmap Index Scan on publication_pkey (cost=0.00..4.42 rows=1 width=0) (actual time=0.295..0.295 rows=1 loops=1) Index Cond: (mtid = 9762715) -> Bitmap Index Scan on publication_last_modifier_btree (cost=0.00..4.42 rows=1 width=0) (actual time=0.674..0.674 rows=0 loops=1) Index Cond: (last_modifier = 21321) Total runtime: 1.027 ms
So weit so gut, schnell und nutzt die verfügbaren Indizes.
Wenn ich eine Abfrage nur ein wenig ändere, lautet das Ergebnis:
SELECT mtid FROM publication
WHERE mtid IN (SELECT 9762715) OR last_modifier=21321
LIMIT 5000;
Die EXPLAIN ANALYZE
Ausgabe ist:
Limit (cost=0.01..2347.74 rows=5000 width=8) (actual time=2735.891..2841.398 rows=1 loops=1) -> Seq Scan on publication (cost=0.01..349652.84 rows=744661 width=8) (actual time=2735.888..2841.393 rows=1 loops=1) Filter: ((hashed SubPlan 1) OR (last_modifier = 21321)) SubPlan 1 -> Result (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1) Total runtime: 2841.442 ms
Nicht so schnell und mit seq scan ...
Natürlich ist die ursprüngliche Abfrage, die von der Anwendung ausgeführt wird, etwas komplexer und sogar langsamer, und das im Ruhezustand generierte Original ist es natürlich nicht (SELECT 9762715)
, aber die Langsamkeit ist auch dafür da (SELECT 9762715)
! Die Abfrage wird im Ruhezustand generiert, daher ist es eine ziemliche Herausforderung, sie zu ändern, und einige Funktionen sind nicht verfügbar (z. B. UNION
nicht verfügbar, was schnell wäre).
Die Fragen
- Warum kann der Index im zweiten Fall nicht verwendet werden? Wie könnten sie verwendet werden?
- Kann ich die Abfrageleistung auf andere Weise verbessern?
Zusätzliche Gedanken
Es scheint, dass wir den ersten Fall verwenden könnten, indem wir manuell SELECT ausführen und dann die resultierende Liste in die Abfrage einfügen. Selbst mit 5000 Zahlen in der IN () -Liste ist es viermal schneller als die zweite Lösung. Es scheint jedoch nur FALSCH (außerdem könnte es 100-mal schneller sein :)). Es ist völlig unverständlich, warum der Abfrageplaner für diese beiden Abfragen eine völlig andere Methode verwendet. Daher möchte ich eine bessere Lösung für dieses Problem finden.
JOIN
anstelle des generiertIN ()
? Außerdem wurdepublication
kürzlich analysiert?(SELECT 9762715)
.(SELECT 9762715)
. Zur Frage zum Ruhezustand: Dies könnte durchgeführt werden, erfordert jedoch ein ernsthaftes Umschreiben des Codes, da wir benutzerdefinierte Kriterienabfragen für den Ruhezustand haben, die im laufenden Betrieb übersetzt werden. Im Wesentlichen würden wir also den Ruhezustand ändern, was ein großes Unterfangen mit vielen möglichen Nebenwirkungen ist.Antworten:
Mein Kollege hat einen Weg gefunden, die Abfrage so zu ändern, dass sie einfach umgeschrieben werden muss und das tut, was sie tun muss, dh die Unterauswahl in einem Schritt und dann die weiteren Operationen am Ergebnis ausführen:
Die EXPLAIN-Analyse lautet nun:
Es scheint, dass wir einen einfachen Parser erstellen können, der alle Unterauswahlen auf diese Weise findet und neu schreibt und ihn einem Hook im Ruhezustand hinzufügt, um die native Abfrage zu bearbeiten.
quelle
SELECT
s zu entfernen , wie Sie es in Ihrer ersten Abfrage in der Frage getan haben?SELECT
separaten und dann die äußere Auswahl mit einer statischen Liste nach demIN
. Dies ist jedoch erheblich langsamer (5-10 Mal, wenn die Unterabfrage mehr als ein paar Ergebnisse enthält), da Sie zusätzliche Netzwerkrundfahrten durchführen und Postgres viele Ergebnisse formatieren und diese Ergebnisse dann von Java analysieren (und dann ausführen) das gleiche nochmal rückwärts). Die obige Lösung macht das gleiche semantisch, während der Prozess innerhalb von Postgres verbleibt. Alles in allem scheint dies derzeit der schnellste Weg mit der kleinsten Änderung in unserem Fall zu sein.Der Kern des Problems wird hier offensichtlich:
Postgres schätzt , dass 744661 Zeilen zurückgegeben werden, während es sich tatsächlich um eine einzelne Zeile handelt. Wenn Postgres nicht besser weiß, was von der Abfrage zu erwarten ist, kann es nicht besser planen. Wir müssten die eigentliche Abfrage dahinter verbergen
(SELECT 9762715)
- und wahrscheinlich auch Tabellendefinition, Einschränkungen, Kardinalitäten und Datenverteilung kennen. Offensichtlich kann Postgres nicht vorhersagen, wie wenige Zeilen von Postgres zurückgegeben werden. Je nachdem, um was es sich handelt, gibt es möglicherweise Möglichkeiten, die Abfrage neu zu schreiben .Wenn Sie wissen, dass die Unterabfrage niemals mehr als
n
Zeilen zurückgeben kann, können Sie Postgres einfach Folgendes mitteilen:Wenn n klein genug ist, wechselt Postgres zu (Bitmap-) Index-Scans. Dies funktioniert jedoch nur für den einfachen Fall. Arbeitet beim Hinzufügen einer
OR
Bedingung nicht mehr: Der Abfrageplaner kann dies derzeit nicht bewältigen.Ich benutze
IN (SELECT ...)
es selten, um damit zu beginnen. In der Regel gibt es eine bessere Möglichkeit, dasselbe zu implementieren, häufig mit einemEXISTS
Semi-Join. Manchmal mit einem (LEFT
)JOIN
(LATERAL
) ...Die offensichtliche Problemumgehung wäre die Verwendung
UNION
, aber Sie haben dies ausgeschlossen. Mehr kann ich nicht sagen, ohne die eigentliche Unterabfrage und andere relevante Details zu kennen.quelle
(SELECT 9762715)
! Wenn ich genau diese Abfrage ausführe, die Sie oben sehen. Natürlich ist die ursprüngliche Abfrage im Ruhezustand etwas komplizierter, aber ich (glaube ich) habe es geschafft, genau zu bestimmen, wo der Abfrageplaner in die Irre geht, und habe diesen Teil der Abfrage vorgestellt. Die obigen Erklärungen und Abfragen sind jedoch wörtlich Strg-Lebenslauf.EXPLAIN ANALYZE SELECT mtid FROM publication WHERE mtid IN (SELECT 9762715 LIMIT 1) OR last_modifier=21321 LIMIT 5000;
auch einen sequentiellen Scan durch und läuft auch ungefähr 3 Sekunden lang ...CREATE TABLE test (mtid bigint NOT NULL, last_modifier bigint, CONSTRAINT test_property_pkey PRIMARY KEY (mtid)); CREATE INDEX test_last_modifier_btree ON test USING btree (last_modifier); INSERT INTO test (mtid, last_modifier) SELECT mtid, last_modifier FROM publication;
. Und der Effekt war immer noch für die gleichen Abfragen datest
: Jede Unterabfrage führte zu einem seq-Scan ... Ich habe sowohl 9.1 als auch 9.4 ausprobiert. Der Effekt ist der gleiche.OR
Bedingung getestet habe . Der Trick mitLIMIT
funktioniert nur für den einfacheren Fall.Antwort auf eine zweite Frage: Ja, Sie können Ihrer Unterabfrage ORDER BY hinzufügen, was sich positiv auswirkt. Die Leistung ähnelt jedoch der Lösung "EXISTS (Unterabfrage)". Selbst bei Unterabfragen, die zu zwei Zeilen führen, gibt es einen signifikanten Unterschied.
quelle