Optimierung teurer Joins / Unterabfragen durch Filtern mit Bedingungen

7

Dies ist eine Frage zum Kurzschließen teurer JOINs oder Unterabfragen in Postgresql (9.5 oder 9.6). Ich bin auch daran interessiert zu hören, wie Leute im Allgemeinen das Check-Then-Execute-Problem lösen.

Ich schreibe viele Abfragen, die nur bedingt ein Ergebnis zurückgeben sollten, z. B. ob der (Web-) Benutzer den Datensatz besitzt oder ob der Datensatz geändert wurde. Ich versuche zu verhindern, dass teure Ansichten in Postgresql und mehrere Hin- und Her-Abfragen erstellt werden, um in der Anwendung selbst nach Bedingungen zu suchen. Daher versuche ich, Abfragen zu schreiben, die zuerst den richtigen Datensatz auswählen und anzeigen, welche Bedingungen fehlgeschlagen sind, und nur die auszuführen anzeigen, ob die Bedingungen erfüllt sind.

Dies prüft beispielsweise, ob der (Anwendungs-) Benutzer einen Datensatz besitzt, bevor er zurückgegeben wird:

SELECT is_owner, is_newer, json 
FROM (
     SELECT id, owner = '053bffbc-c41e-dad4-853b-ea91fc42ea18' "is_owner"
          , modified >= created "is_newer" 
     FROM datasets 
     WHERE id = '056e4eed-ee63-2add-e981-0c86b8b6a66f'
) cond
LEFT JOIN LATERAL (
     SELECT id 
     FROM datasets 
     WHERE is_owner and is_newer
) authed
    ON cond.id = authed.id
LEFT JOIN LATERAL (
     SELECT json 
     FROM view_dataset 
     WHERE id = authed.id
) dataset
    ON true;

Ergebnis (ist Eigentümer):

is_owner | is_newer | json
t          t          {...}

Und ein negatives Ergebnis (nicht Eigentümer):

is_owner | is_newer | json
f          t          NULL

Die Anwendung weiß also, welcher Fehler zurückgegeben werden soll, aber wir müssen die Ansicht nicht erstellen oder analysieren, wenn die Bedingungen nicht erfüllt sind.

EXPLAIN ANALYZE zeigt jedoch, dass Postgresql die Ansichtsabfrage im letzten LEFT LATERAL JOIN weiterhin ausführt, obwohl der mittlere JOIN keine Ergebnisse hat , und ich kann ihn nicht kurzschließen, um zu verhindern, dass das (teure) view_dataset SELECT ausgeführt wird . Wenn ich jsonauf nulldie Abfrage setze , wird alles außer dem ersten SELECT übersprungen. Wenn es jedoch auf einen Wert aus der letzten Abfrage festgelegt ist, werden immer alle SELECTs ausgeführt. Ich denke, der Abfrageplaner ist der Meinung, dass er ein Ergebnis für dieses jsonFeld in der obersten SELECT-Abfrage erhalten muss, und schließt die JOINs nicht kurz .

Ich frage mich, ob ich Postgresql zwingen kann, die teure Ansichtsabfrage zu löschen.

Ich habe auch einen CTE ausprobiert, der die JOIN-Abfrage zu überspringen scheint:

WITH cond as (
    SELECT id, owner = '053bffbc-c41e-dad4-853b-ea91fc42ea18' "is_owner", modified >= created "is_newer" FROM datasets WHERE id = '056e4eed-ee63-2add-e981-0c86b8b6a66f'
)
SELECT cond.id, cond.is_owner, cond.is_newer, json FROM
    (SELECT id FROM cond WHERE cond.is_owner and cond.is_newer) filtered
    LEFT JOIN LATERAL
    (SELECT id, json from view_dataset) dataset
    USING (id)
    RIGHT JOIN cond
    USING(id);

... aber diese Abfrage und Variationen sind mindestens 2x langsamer.

Meine Frage ist also, wie die Leistung maximiert werden kann, indem JOINs oder Unterabfragen basierend auf den Bedingungen kurzgeschlossen werden. und ich bin auch interessiert zu hören, ob jemand andere Ideen hat, wie man ein Muster, bei dem zuerst geprüft und dann ausgeführt wird, implementiert, z. B. das Überprüfen des Datensatzbesitzes.

wvh
quelle
Keine Antwort auf Ihre Frage, aber Sie könnten Interesse an dem folgenden Artikel von dba.stackexchange.com/users/2512/lukas-eder finden : blog.jooq.org/2017/09/28/…
Lennart
Was passiert, wenn Sie wechseln: ) dataset ON true;zu ) dataset ON is_owner;?
Lennart

Antworten:

1

Nicht sicher, warum überhaupt authedbenötigt wird. Was macht:

SELECT is_owner, is_newer, json 
FROM (
     SELECT id, owner = '053bffbc-c41e-dad4-853b-ea91fc42ea18' "is_owner"
          , modified >= created "is_newer" 
     FROM datasets 
     WHERE id = '056e4eed-ee63-2add-e981-0c86b8b6a66f'
) cond
LEFT JOIN LATERAL (
     SELECT json 
     FROM view_dataset 
     WHERE id = cond.id
) dataset
    ON is_owner and is_newer;

von dir? Ich stimme auch dem Kommentar von a_horse_with_no_name zu . LATERAL kann in besonderen Fällen eine enorme Hilfe sein, um Prädikate in die Basistabellen zu verschieben. Es handelt sich jedoch nur um eine getarnte Unterabfrage. In den meisten Fällen ist es daher sinnvoller, einen normalen Join durchzuführen. Versuchen Sie auch:

SELECT is_owner, is_newer, json 
FROM (
     SELECT id, owner = '053bffbc-c41e-dad4-853b-ea91fc42ea18' "is_owner"
          , modified >= created "is_newer" 
     FROM datasets 
     WHERE id = '056e4eed-ee63-2add-e981-0c86b8b6a66f'
) cond
LEFT JOIN view_dataset wd
    ON wd.id = cond.id 
   AND cond.is_owner 
   AND cond.is_newer;

BEARBEITEN. Tabellenwertfunktion

CREATE FUNCTION get_view_dataset(int,bool) 
    RETURNS setof view_dataset AS '
        SELECT * 
        FROM view_dataset wd
        WHERE wd.id = $1 
          AND $2;
    ' LANGUAGE SQL;

und verwenden Sie diese Funktion dann in Ihrer Abfrage als:

SELECT is_owner, is_newer, json 
FROM (
     SELECT id, owner = '053bffbc-c41e-dad4-853b-ea91fc42ea18' "is_owner"
          , modified >= created "is_newer" 
     FROM datasets 
     WHERE id = '056e4eed-ee63-2add-e981-0c86b8b6a66f'
) cond
LEFT JOIN get_view_dataset(cond.id, cond.is_owner AND cond.is_newer);

Alles ungetestet.

Lennart
quelle
Zusätzlich: Eine seitliche Verbindung ist nicht unbedingt schneller. Ich würde auch versuchenleft join view_dataset as dataset on dataset.id = cond.id and cond.is_owner and cond.is_newer
a_horse_with_no_name
Ich stimme zu, ich habe meine Antwort aktualisiert. Wenn Sie eine eigene Antwort hinzufügen möchten, kann ich diesen Teil entfernen.
Lennart
Diese funktionieren alle einwandfrei, führen aber immer noch die teure Ansicht in der letzten Auswahl aus (zumindest bei 9.5). Die mittlere seitliche Verknüpfung wird benötigt, um von 1 übereinstimmenden Datensatz in der ersten Abfrage auf Null in der dritten (tatsächliche Ansicht) zu wechseln, sodass die dritte Ansicht niemals ausgeführt wird, wenn dies nicht erforderlich ist. Der Planer besteht darauf, diese Ansicht auszuführen, wenn trotz der Bedingungen ein Feld davon in der Hauptauswahl erwähnt wird (es ist das "json" -Feld).
wvh
Hilft eine Tabellenwertfunktion (eine Art parametrisierte Ansicht)? Ich werde ein Beispiel hinzufügen.
Lennart
1
Ich habe eine SQL-Geige erstellt , um leichter erkennen zu können, worauf Gong läuft.
wvh