it-swarm.com.de

Optimierung teurer Joins / Unterabfragen durch Filtern mit Bedingungen

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 Postgresql führt die Ansichtsabfrage immer noch im letzten LEFT LATERAL JOIN aus, obwohl der mittlere JOIN keine Ergebnisse hat, und ich kann ihn nicht kurzschließen, um das zu verhindern (teuer) view_dataset SELECT wird nicht ausgeführt. Wenn ich json auf null setze, überspringt die Abfrage alles außer dem ersten SELECT; Wenn es jedoch auf einen Wert aus der letzten Abfrage festgelegt ist, werden immer alle SELECTs ausgeführt. Ich denke, der Abfrageplaner glaubt, dass er ein Ergebnis für dieses json -Feld in der obersten SELECT-Abfrage erhalten muss, und tut dies nicht. t Schließen Sie die JOINs 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 ein Muster, bei dem zuerst geprüft und dann ausgeführt wird, implementiert werden kann, z. B. die Überprüfung des Datensatzbesitzes.

7
wvh

Ich bin mir nicht sicher, warum authed überhaupt benö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.

1
Lennart