it-swarm.com.de

PostgreSQL-Abfrage sehr langsam, wenn Unterabfrage hinzugefügt wird

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 nun eine Abfrage nur ein wenig ändere, lautet das Ergebnis:

SELECT mtid FROM publication
WHERE mtid IN (SELECT 9762715) OR last_modifier=21321
LIMIT 5000;

Das 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 natürlich ist das im Ruhezustand generierte Original 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. ist UNION nicht verfügbar, was schnell wäre).

Die Fragen

  1. Warum kann der Index im zweiten Fall nicht verwendet werden? Wie könnten sie verwendet werden?
  2. 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.

9
P.Péter

Mein Kollege hat einen Weg gefunden, die Abfrage so zu ändern, dass sie einfach umgeschrieben werden muss und das tut, was sie tun muss, d. H. Die Unterauswahl in einem Schritt und dann die weiteren Operationen am Ergebnis ausführen:

SELECT mtid FROM publication 
WHERE 
  mtid = ANY( (SELECT ARRAY(SELECT 9762715))::bigint[] )
  OR last_modifier=21321
LIMIT 5000;

Die EXPLAIN-Analyse lautet nun:

 Limit  (cost=92.58..9442.38 rows=2478 width=8) (actual time=0.071..0.074 rows=1 loops=1)
   InitPlan 2 (returns $1)
     ->  Result  (cost=0.01..0.02 rows=1 width=0) (actual time=0.010..0.011 rows=1 loops=1)
           InitPlan 1 (returns $0)
             ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.002 rows=1 loops=1)
   ->  Bitmap Heap Scan on publication  (cost=92.56..9442.36 rows=2478 width=8) (actual time=0.069..0.070 rows=1 loops=1)
         Recheck Cond: ((mtid = ANY (($1)::bigint[])) OR (last_modifier = 21321))
         Heap Blocks: exact=1
         ->  BitmapOr  (cost=92.56..92.56 rows=2478 width=0) (actual time=0.060..0.060 rows=0 loops=1)
               ->  Bitmap Index Scan on publication_pkey  (cost=0.00..44.38 rows=10 width=0) (actual time=0.046..0.046 rows=1 loops=1)
                     Index Cond: (mtid = ANY (($1)::bigint[]))
               ->  Bitmap Index Scan on publication_last_modifier_btree  (cost=0.00..46.94 rows=2468 width=0) (actual time=0.011..0.011 rows=0 loops=1)
                     Index Cond: (last_modifier = 21321)
 Planning time: 0.704 ms
 Execution time: 0.153 ms

Anscheinend können wir einen einfachen Parser erstellen, der alle Unterauswahlen auf diese Weise findet und neu schreibt, und ihn einem Hook im Ruhezustand hinzufügen, um die native Abfrage zu bearbeiten.

6
P.Péter

Der Kern des Problems wird hier offensichtlich:

Seq Scan bei Veröffentlichung (Kosten = 0,01..349652,84 Zeilen = 744661 Breite = 8) (tatsächliche Zeit = 2735,888..2841,393 Zeilen = 1 Schleifen = 1)

Postgres schätzt , dass 744661 Zeilen zurückgegeben werden, während sich herausstellt, dass 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 hinter (SELECT 9762715) Verstecken sehen - und wahrscheinlich auch Tabellendefinition, Einschränkungen, Kardinalitäten und Datenverteilung kennen. Offensichtlich kann Postgres nicht vorhersagen, wie wenige Zeilen von Postgres zurückgegeben werden. Es kann verschiedene Möglichkeiten geben, die Abfrage neu zu schreiben, je nachdem, was sie ist .

Wenn Sie wissen , dass die Unterabfrage niemals mehr als n zurückgeben kann Zeilen, Sie können Postgres einfach sagen, indem Sie:

SELECT mtid
FROM   publication
WHERE  mtid IN (SELECT ... LIMIT n) --  OR last_modifier=21321
LIMIT  5000;

Wenn n klein genug ist, wechselt Postgres zu (Bitmap-) Index-Scans. jedoch, das funktioniert nur für den einfachen Fall. Arbeitet nicht mehr, wenn eine Bedingung OR hinzugefügt wird: Der Abfrageplaner kann dies derzeit nicht bewältigen.

Ich benutze selten IN (SELECT ...), um damit zu beginnen. Normalerweise gibt es eine bessere Möglichkeit, dasselbe zu implementieren, häufig mit einem EXISTS Semi-Join. Manchmal mit einem (LEFT) JOIN (LATERAL) ...

Die offensichtliche Problemumgehung wäre die Verwendung von UNION, aber Sie haben dies ausgeschlossen. Mehr kann ich nicht sagen, ohne die eigentliche Unterabfrage und andere relevante Details zu kennen.

5

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.

SELECT mtid FROM publication
WHERE mtid IN (SELECT #column# ORDER BY #column#) OR last_modifier=21321
LIMIT 5000;
1
iki