Seltsamer Optimierungseffekt mit WITH-Konstruktion


7

Ich habe 4 Tabellen, nennen wir sie:

  1. Tabelle A, 15 Millionen Zeilen
  2. Tabelle B, 40 KB Zeilen,
  3. Tabelle C, 30K Zeilen,
  4. Tabelle D, 25 Millionen Zeilen

(kk - bedeutet Millionen)

und ich hatte eine Legacy-Abfrage, die wie folgt aufgebaut war:

select C.<some_fields>,B.<some_fields>,D.<some_fields> from C
inner join A on C.x = A.x
inner join D on D.z = 123 and D.a_id = A.a_id
inner join B on C.x = B.x and B.z = 123
where A.type = 'Xxx'

Diese Abfrage war extrem langsam. Die Ausführung der Ergebnisse dauerte bis zu 3 Minuten (in bestimmten Fällen werden 35.000 Zeilen zurückgegeben).

Aber wenn ich es in die folgende Struktur geändert habe:

with t as (
   select C.<some_fields>,D.<some_fields> from C
   inner join A on C.x = A.x
   inner join D on D.z = 123 and D.a_id = A.a_id
   where A.type = 'Xxx'
)
select t.*, B.<some_fields>,
inner join B on t.x = B.x and B.z = 123

Es begann 30-mal schneller zu arbeiten (dh es dauert jetzt bis zu 6 Sekunden, um dieselben Ergebnisse abzurufen).

Nehmen wir an, dass Indizes richtig aufgebaut sind. Und meine Idee, einen solchen Trick zu machen, ist entstanden, als ich bemerkte, dass dieser Block, in den ich eingewickelt habe, with ( ... )sehr schnell funktioniert (und eine sehr ähnliche Datenmenge wie die gesamte Abfrage zurückgibt).

Meine Frage ist also: Was könnte der Grund sein? Warum kann Postgres intern keinen richtigen Plan erstellen oder denselben Trick ausführen?

UPDATE :

Ausführungsplan für alte Abfrage :

Nested Loop  (cost=1.83..1672.82 rows=1 width=54) (actual time=8.178..91515.625 rows=37373 loops=1)
  ->  Nested Loop  (cost=1.42..1671.47 rows=1 width=62) (actual time=8.108..90883.567 rows=37373 loops=1)
        Join Filter: (a.x = b.x)
        Rows Removed by Join Filter: 9132436
        ->  Index Scan using b_pkey on B b  (cost=0.41..8.43 rows=1 width=71) (actual time=0.022..0.782 rows=241 loops=1)
              Index Cond: (z = 123)
        ->  Nested Loop  (cost=1.00..1660.48 rows=146 width=149) (actual time=0.027..363.227 rows=38049 loops=241)
              ->  Index Only Scan using idx_1 on D d  (cost=0.56..424.59 rows=146 width=8) (actual time=0.017..50.869 rows=64176 loops=241)
                    Index Cond: (z = 123)
                    Heap Fetches: 15564503
              ->  Index Scan using a_pkey on A a  (cost=0.44..8.46 rows=1 width=149) (actual time=0.003..0.004 rows=1 loops=15466416)
                    Index Cond: (a_id = d.a_id)
  ->  Index Scan using c_pkey on C c (cost=0.41..1.08 rows=1 width=8) (actual time=0.005..0.007 rows=1 loops=37373)
        Index Cond: (x = a.x)
        Filter: ((type)::text = 'Xxx')
Planning time: 3.468 ms
Execution time: 91541.019 ms

Ausführungsplan für neue Abfrage :

Hash Join  (cost=1828.09..1830.28 rows=1 width=94) (actual time=0.654..1130.542 rows=37376 loops=1)
  Hash Cond: (t.x = b.x)
  CTE t
    ->  Nested Loop  (cost=1.42..1819.64 rows=81 width=158) (actual time=0.060..761.058 rows=38052 loops=1)
          ->  Nested Loop  (cost=1.00..1660.48 rows=146 width=149) (actual time=0.039..461.235 rows=38052 loops=1)
                ->  Index Only Scan using idx_1 on D d  (cost=0.56..424.59 rows=146 width=8) (actual time=0.024..73.972 rows=64179 loops=1)
                      Index Cond: (z = 123)
                      Heap Fetches: 64586
                ->  Index Scan using a_pkey on A a  (cost=0.44..8.46 rows=1 width=149) (actual time=0.004..0.004 rows=1 loops=64179)
                      Index Cond: (a_id = d.a_id)
          ->  Index Scan using c_pkey on C c  (cost=0.41..1.07 rows=1 width=17) (actual time=0.004..0.005 rows=1 loops=38052)
                Index Cond: (x = a.x)
                Filter: ((type)::text = 'Xxx')
  ->  CTE Scan on t  (cost=0.00..1.62 rows=81 width=104) (actual time=0.063..854.405 rows=38052 loops=1)
  ->  Hash  (cost=8.43..8.43 rows=1 width=71) (actual time=0.353..0.353 rows=241 loops=1)
        Buckets: 1024  Batches: 1  Memory Usage: 34kB
        ->  Index Scan using b_pkey on B b  (cost=0.41..8.43 rows=1 width=71) (actual time=0.012..0.262 rows=241 loops=1)
              Index Cond: (z = 123)
Planning time: 1.221 ms
Execution time: 1147.267 ms

UPDATE-2 :

Kürzlich haben liebe Kommentatoren bemerkt, dass das Problem durch schlechte Schätzungen der Zeilennummer verursacht wurde, und sie haben mir vorgeschlagen, dies zu tun vacuum analyze. Aber ich verwende diesen Server dort, Amazon-RDSwo die autovacuumFunktion aktiviert ist. Außerdem habe ich versucht, das in der Amazon RDS-Dokumentation vorgeschlagene Skript zum Anzeigen von Tabellen auszuführen, die für Vakuum geeignet sind , und es zeigt mir 0 Tabellen, die für Vakuum geeignet sind.

UPDATE-3 : Die ANALYZEin den Kommentaren vorgeschlagene Ausführung hat die Schätzungen für fehlerhafte Zeilen in den Plänen nicht geändert, sondern die Geschwindigkeit der "alten" Abfragevariante erhöht. Ich habe immer noch kein vollständiges Verständnis für meine Kernfrage: Warum hat der zweite Abfragetyp eine dramatisch höhere Geschwindigkeit (sogar ohne ANALYSE)?


5
Häufige Tabellenausdrücke ( WITHAbfragen) in PostgreSQL sind Optimierungszäune , weshalb sie nicht auf die ursprüngliche Form reduziert werden. Es sieht so aus, als hätten Sie ein Problem mit der Schätzung der Verbindungsreihenfolge, was wahrscheinlich statistische Probleme oder so etwas wie stark korrelierte Felder bedeutet, die zu schlechten Schätzungen führen. Sie haben keine Abfragepläne angegeben, daher ist es schwierig, mehr zu sagen.
Craig Ringer

2
Bearbeitet, um Pläne auf EXPLAIN.DEPESZ.com zum leichteren Lesen anzuzeigen. Beachten Sie die schlechten Zeilenanzahlschätzungen? Das ist die Ursache des Problems.
Craig Ringer

Antworten:


1

Aus der Erklärung geht klar hervor, dass PostgreSQL schlechte Schätzungen erstellt und einen schlechten Plan für die erste Abfrage ausgewählt hat. Der Gleichheitswähler in Spalte z in Tabelle D hatte eine völlig falsche Schätzung (~ 500-fache Abweichung).

Das zweite Mal war besser, weil WITH ein Planungszaun ist, wie Craig betonte.

Konzentrieren wir uns auf die erste Abfrage.

Der Planer erstellt schlechte Schätzungen aufgrund fehlender / alter / unzureichender Statistiken.

Fast alle Statistiken sind in sichtbar pg_stats. Sie sollten diese Ansicht überprüfen und vorzugsweise relevante Zeilen hier einfügen.

SELECT * FROM pg_stats WHERE tablename='d' and attname='z';

Wenn eine Spalte eine lustige statistische Verteilung aufweist (ungleichmäßig, nicht gaußsch, schief, pseudozufällig mit versteckten Mustern usw.), kann das Statistikmodul hinter ANALYZE möglicherweise die für den Planer erforderlichen Regelmäßigkeiten nicht erfassen.

In vielen Fällen hilft die Verwendung einer größeren Stichprobe dabei, wahrheitsgemäße Schätzungen zu erstellen. Es gibt einen Konfigurationsparameter zum Erhöhen der Stichprobengröße für ANALYZE , der folgendermaßen verwendet werden kann:

SET default_statistics_target TO 200;
ANALYZE A;
ANALYZE B;
ANALYZE C;
SET default_statistics_target TO 1000;
ANALYZE D;

Experimentieren Sie mit Werten und wiederholen Sie Ihre SELECT-Abfragen. Wenn es hilft, können Sie die Stichprobengröße mithilfe von dauerhaft erhöhen ALTER TABLE D ALTER COLUMN Z SET STATISTICS 1000.

PS. Wie immer sollten Sie sicherstellen, dass die Speicher- / Ressourcenkonfiguration korrekt ist. Alle Einstellungen im Abschnitt "Ressourcenkonfiguration" sollten an die tatsächlichen Serverressourcen angepasst werden (Datenbank- und Speichergröße, RAID-Array-Typ, SSD-Laufwerke).


Das Problem ist, dass es sich um eine Produktionsdatenbank handelt, sodass keine Möglichkeit besteht, Experimente damit durchzuführen.
Andremoniy

Dies ist kein Problem, da der SET-Befehl nur die aktuelle Sitzung betrifft. Sie können neue Einstellungen sicher testen, bevor Sie ALTER ausführen.
Filiprem
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.