2013-07-10 8 views
8

Ich habe eine Abfrage auf einem Postgresql 9.2-System, das ungefähr 20s in seiner normalen Form dauert, aber dauert nur ~ 120ms bei der Verwendung eines CTE.Gibt es eine logisch äquivalente und effiziente Version dieser Abfrage ohne Verwendung eines CTE?

Ich habe beide Abfragen der Kürze halber vereinfacht.

Hier ist die normale Form (dauert ca. 20s): http://explain.depesz.com/s/2v8

Der CTE Form (ca. 120ms):

WITH raw AS (
    SELECT * 
    FROM tableA 
    WHERE (columna = 1 OR columnb = 2) AND 
     atype = 35 AND 
     aid IN (1, 2, 3) 
) 
SELECT * 
FROM raw 
ORDER BY modified_at DESC 
LIMIT 25; 

SELECT * 
FROM tableA 
WHERE (columna = 1 OR columnb = 2) AND 
    atype = 35 AND 
    aid IN (1, 2, 3) 
ORDER BY modified_at DESC 
LIMIT 25; 

Hier wird die für diese Abfrage erklären Hier ist die Erklärung für die CTE: http://explain.depesz.com/s/uxy

Einfach durch Verschieben Die ORDER BY im äußeren Teil der Abfrage reduziert die Kosten um 99%.

Ich habe zwei Fragen: 1) gibt es eine Möglichkeit, die erste Abfrage ohne Verwendung eines CTE so zu erstellen, dass es logisch äquivalenter leistungsfähiger ist und 2) was sagt dieser Unterschied in der Leistung darüber aus, wie der Planer ist Bestimmen, wie man die Daten holt?

In Bezug auf die oben genannten Fragen gibt es zusätzliche Statistiken oder andere Planer Hinweise, die die Leistung der ersten Abfrage zu verbessern helfen würde?

Edit: Das Entfernen des Limits führt auch dazu, dass die Abfrage einen Heap-Scan im Gegensatz zu einem Index-Scan rückwärts verwendet. Ohne die LIMIT wird die Abfrage in 40ms abgeschlossen.

Nach der Wirkung des LIMIT da ich mit LIMIT 1 versucht, LIMIT 2 usw. Die Abfrage führt unter 100ms in, wenn LIMIT 1 und 10s mit + mit LIMIT> 1.

Nachdem darüber nachgedacht etwas mehr, Frage 2 kocht Warum verwendet der Planer einen Index-Scan rückwärts in einem Fall und einen Bitmap-Heap-Scan + Sortierung in einem anderen logisch äquivalenten Fall? Und wie kann ich dem Planer helfen, in beiden Fällen den effizienten Plan zu verwenden?


Update: I Craig Antwort akzeptiert, weil es die umfassendste und hilfreich war. Die Art und Weise, wie ich das Problem gelöst habe, war die Verwendung einer Abfrage, die praktisch äquivalent, aber nicht logisch äquivalent war. Am Anfang des Problems lag ein Index-Scan rückwärts des Indexes von modified_at. Um dem Planer mitzuteilen, dass dies keine gute Idee war, füge ich ein Prädikat des Formulars WHERE modified_at >= NOW() - INTERVAL '1 year' hinzu. Dies beinhaltete genügend Daten für die Anwendung, verhinderte jedoch, dass der Planer den Rückwärts-Index-Scan-Pfad durchging.

Dies war eine viel geringere Auswirkung Lösung, die die Notwendigkeit entweder eine Sub-Abfrage oder einen CTE unter Verwendung der Abfragen zu umschreiben verhindert. YMMV.

Antwort

10

Hier laufen, warum dies geschieht, mit der folgenden Erklärung Strom bis mindestens 9.3 (Wenn Sie dies und eine neuere Version lesen, stellen Sie sicher, dass es sich nicht geändert hat):

PostgreSQL optimiert nicht über CTE-Grenzen hinweg. Jede CTE-Klausel wird isoliert ausgeführt, und ihre Ergebnisse werden von anderen Teilen der Abfrage verbraucht. Also eine Abfrage wie:

wird dazu führen, dass die vollständige innere Abfrage ausgeführt wird. PostgreSQL wird die id = 4 Qualifikation nicht in die innere Abfrage "drücken". CTEs sind in dieser Hinsicht "Optimierungszäune", die sowohl gut als auch schlecht sein können; Wenn Sie möchten, können Sie den Planer überschreiben, aber Sie können CTEs nicht als einfache syntaktische Bereinigung für eine tief verschachtelte Unterabfragekette verwenden, wenn Sie Push-down benötigen.

Wenn Sie die oben als umformulieren:

SELECT * 
FROM (SELECT * FROM some_table) AS blah 
WHERE id = 4; 

mit einer Unterabfrage in FROM anstelle einem CTE, Pg die qual nach unten in die Unterabfrage drücken und es wird alles läuft schön und schnell.

Wie Sie festgestellt haben, kann dies auch zu Ihrem Vorteil funktionieren, wenn der Abfrageplaner eine schlechte Entscheidung trifft. Es scheint, dass in Ihrem Fall ein Index Index Scan der Tabelle ist immens teurer eine Bitmap oder Index-Scan von zwei kleineren Indizes gefolgt von einem Filter und Sortieren, aber der Planer denkt nicht, dass es so sein wird, plant es die Abfrage um den Index zu scannen.

Wenn Sie das CTE verwenden, kann es die ORDER BY nicht in die innere Abfrage schieben, also überschreiben Sie seinen Plan und zwingen ihn zu verwenden, was er denkt, ist ein schlechter Ausführungsplan - aber eine, die sich herausstellt viel besser.

Es gibt eine unangenehme Problemumgehung, die für diese Situationen verwendet werden kann, genannt der OFFSET 0 Hack, aber Sie sollten es nur verwenden, wenn Sie nicht einen Weg finden, den Planer das Richtige zu tun - und wenn Sie verwenden müssen Bitte koche dies auf einen eigenständigen Testfall und melde ihn als möglichen Fehler bei der Abfrageplanung an die PostgreSQL-Mailingliste.

Stattdessen empfehle ich zuerst Blick auf , warum der Planer die falsche Entscheidung trifft.

Der erste Kandidat ist Statistiken/Schätzungen Probleme, und sicher genug, wenn wir Ihren problematischen Abfrageplan betrachten, gibt es einen Faktor von 3500 Fehleinschätzung der erwarteten Ergebniszeilen. Das ist groß, aber nicht unglaublich groß, obwohl es interessanter ist, dass Sie nur eine Zeile erhalten, wo der Planer einen nicht-trivialen Zeilensatz erwartet. Das hilft uns allerdings nicht viel. Wenn die Anzahl der Zeilen niedriger als erwartet ist, bedeutet dies, dass die Entscheidung, den Index zu verwenden, eine bessere Wahl war als erwartet.

Das Hauptproblem sieht aus wie es nicht den kleineren, selektiven Indizes mit sierra_kilo und papa_lima, weil es die ORDER BY sieht und denkt, dass es mehr Zeit damit einen Rückwärts Index-Scan speichern werden und die Vermeidung die Art, als es wirklich tut . Das macht Sinn, da nur eine passende Zeile zu sortieren ist! Wenn es die erwarteten 3500 Zeilen hat, dann wäre es vielleicht sinnvoller gewesen, die Sortierung zu vermeiden, obwohl das immer noch ein ziemlich kleines Rowset ist, das nur im Speicher sortiert wird.

Haben Sie irgendwelche Parameter wie enable_seqscan, etc? Wenn du dies tust, lösche sie; Sie sind für Test nur und völlig ungeeignet für den Produktionseinsatz.Wenn Sie nicht die enable_ Params verwenden, denke ich, es lohnt sich, dies auf der PostgreSQL-Mailingliste pgsql-perform zu erhöhen. Die anonymisierten Pläne machen dies jedoch ein wenig schwierig, zumal es keine Garantie gibt, dass Bezeichner aus einem Plan auf die gleichen Objekte in dem anderen Plan verweisen, und sie stimmen nicht mit dem überein, was Sie in der Abfrage zu der Frage geschrieben haben. Sie sollten eine korrekt handgemachte Version erstellen, in der alles übereinstimmt, bevor Sie auf der Mailingliste nachfragen.

Es gibt eine ziemlich gute Chance, dass Sie die realen Werte für jeden zur Verfügung stellen müssen, um zu helfen. Wenn Sie das nicht in einer öffentlichen Mailingliste tun möchten, geben Sie there's another option available ein. (Ich sollte beachten, dass ich für eines von ihnen arbeite, nach meinem Profil).

+0

danke, obwohl ich diese Eigenschaft genutzt haben (dies ist ein Beispiel) Ich wusste nicht, dass die PostgreSQL nicht über CTE Grenzen optimiert. Wenn Sie sich die von mir zur Verfügung gestellten EXPLAIN-Pläne ansehen, scheint es nicht so, dass nennenswerte Mengen von "work_mem" verwendet werden (~ 25k). Der Großteil der Kosten kommt vom Index-Scan rückwärts. – drsnyder

+0

@drsnyder Oh! Ich habe falsch gelesen! 20s und 120ms. Ich werde die Antwort erneut lesen und anpassen. –

+0

@drsnyder neu geschrieben. Hoffnung, die mehr Sinn macht. Bitte zeigen Sie die Ausgabe von http://wiki.postgresql.org/wiki/Server_Configuration an, nur um zu bestätigen, dass Sie keine 'enable_' -Parameter haben, usw., aber es sieht nach einer etwas zwielichtigen Wahl des Planers aus. –

2

Nur ein Schuss im Dunkeln, aber was passiert, wenn man diese

SELECT * 
FROM (
    SELECT * 
    FROM tableA 
    WHERE (columna = 1 OR columnb = 2) AND 
     atype = 35 AND 
     aid IN (1, 2, 3) 
) AS x 
ORDER BY modified_at DESC 
LIMIT 25;