Warum läuft eine Set Returning Function (SRF) in einer FROM-Klausel langsamer?

8

Dies ist eine datenbankinterne Frage. Ich verwende PostgreSQL 9.5 und frage mich, warum Set Returning Functions (SRFs), auch als TVFs (Table Valued Functions) bezeichnet, in einer FROMKlausel langsamer ausgeführt werden , z. B. wenn ich diese Befehle ausführe.

CREATE TABLE foo AS SELECT * FROM generate_series(1,1e7);
SELECT 10000000
Time: 5573.574 ms

Es ist immer wesentlich langsamer als,

CREATE TABLE foo AS SELECT generate_series(1,1e7);
SELECT 10000000
Time: 4622.567 ms

Gibt es eine allgemeine Regel , die hier gemacht werden kann, so dass wir immer Funktionen-Set Rückkehr außerhalb einer laufen FROMKlausel?

Evan Carroll
quelle

Antworten:

13

Beginnen wir mit dem Vergleich der Ausführungspläne:

tinker=> EXPLAIN ANALYZE SELECT * FROM generate_series(1,1e7);
                                                           QUERY PLAN                                                           
--------------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series  (cost=0.00..10.00 rows=1000 width=32) (actual time=2382.582..4291.136 rows=10000000 loops=1)
 Planning time: 0.022 ms
 Execution time: 5539.522 ms
(3 rows)

tinker=> EXPLAIN ANALYZE SELECT generate_series(1,1e7);
                                           QUERY PLAN                                            
-------------------------------------------------------------------------------------------------
 Result  (cost=0.00..5.01 rows=1000 width=0) (actual time=0.008..2622.365 rows=10000000 loops=1)
 Planning time: 0.045 ms
 Execution time: 3858.661 ms
(3 rows)

Okay, jetzt wissen wir, dass dies SELECT * FROM generate_series()mit einem Function ScanKnoten ausgeführt wird, während SELECT generate_series()es mit einem ResultKnoten ausgeführt wird. Was auch immer dazu führt, dass diese Abfragen unterschiedlich ausgeführt werden, läuft auf den Unterschied zwischen diesen beiden Knoten hinaus, und wir wissen genau, wo wir suchen müssen.

Eine weitere interessante Sache in der EXPLAIN ANALYZEAusgabe: Beachten Sie die Timings. SELECT generate_series()ist actual time=0.008..2622.365, während SELECT * FROM generate_series()ist actual time=2382.582..4291.136. Der Function ScanKnoten beginnt ungefähr zu dem Zeitpunkt, an dem der ResultKnoten die Rückgabe von Datensätzen beendet hat, mit der Rückgabe von Datensätzen.

Was machte PostgreSQL zwischen t=0und t=2382im Function ScanPlan? Anscheinend dauert es ungefähr so ​​lange, bis es läuft generate_series(), also würde ich wetten, dass es genau das ist, was es getan hat. Die Antwort nimmt Gestalt an: Es scheint, als würden Resultdie Ergebnisse sofort zurückgegeben, während Function Scandie Ergebnisse scheinbar materialisiert und dann gescannt werden.

Mit dem EXPLAINaus dem Weg, lassen Sie sich die Umsetzung überprüfen. Der ResultKnoten lebt in nodeResult.c, der sagt:

 * DESCRIPTION
 *
 *      Result nodes are used in queries where no relations are scanned.

Der Code ist einfach genug.

Function Scanlebt in nodeFunctionScan.c, und in der Tat scheint es eine zweiphasige Ausführungsstrategie zu nehmen :

/*
 * If first time through, read all tuples from function and put them
 * in a tuplestore. Subsequent calls just fetch tuples from
 * tuplestore.
 */

Und aus Gründen der Klarheit wollen wir sehen, was a tuplestoreist :

 * tuplestore.h
 *    Generalized routines for temporary tuple storage.
 *
 * This module handles temporary storage of tuples for purposes such
 * as Materialize nodes, hashjoin batch files, etc.  It is essentially
 * a dumbed-down version of tuplesort.c; it does no sorting of tuples
 * but can only store and regurgitate a sequence of tuples.  However,
 * because no sort is required, it is allowed to start reading the sequence
 * before it has all been written.  This is particularly useful for cursors,
 * because it allows random access within the already-scanned portion of
 * a query without having to process the underlying scan to completion.
 * Also, it is possible to support multiple independent read pointers.
 *
 * A temporary file is used to handle the data if it exceeds the
 * space limit specified by the caller.

Hypothese bestätigt. Function Scanwird im Voraus ausgeführt und materialisiert die Ergebnisse der Funktion, was bei großen Ergebnismengen zu einem Verschütten auf die Festplatte führt. Resultmaterialisiert nichts, unterstützt aber auch nur triviale Operationen.

Willglynn
quelle