Perché una funzione di ritorno (SRF) viene eseguita più lentamente in una clausola FROM?


8

Questa è una domanda interna al database. Sto usando PostgreSQL 9.5, mi chiedo perché Set Returning Functions (SRFs), noto anche come Table Valued Functions (TVFs) funzioni più lentamente quando in una FROMclausola, ad esempio quando eseguo questi comandi,

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

È sempre sostanzialmente più lento di,

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

Esiste una regola generale che può essere fatta qui, in modo tale che dovremmo sempre eseguire le funzioni di restituzione al di fuori di una FROMclausola?

Risposte:


13

Cominciamo confrontando i piani di esecuzione:

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)

Bene, ora sappiamo che SELECT * FROM generate_series()viene eseguito utilizzando un Function Scannodo, mentre SELECT generate_series()viene eseguito utilizzando un Resultnodo. Qualunque cosa stia causando l'esecuzione di queste query in modo diverso, si riduce alla differenza tra questi due nodi e sappiamo esattamente dove cercare.

Un'altra cosa interessante EXPLAIN ANALYZEnell'output: notare i tempi. SELECT generate_series()è actual time=0.008..2622.365, mentre SELECT * FROM generate_series()è actual time=2382.582..4291.136. Il Function Scannodo inizia a restituire i record nel momento in cui il Resultnodo ha finito di restituire i record.

Cosa stava facendo PostgreSQL tra t=0e t=2382nel Function Scanpiano? Apparentemente si tratta di quanto tempo ci vuole per correre generate_series(), quindi scommetto che è esattamente quello che stava facendo. La risposta inizia a prendere forma: sembra che Resultrestituisca immediatamente i risultati, mentre sembra Function Scanmaterializzare i risultati e quindi scansionarli.

Con il EXPLAINfuori mano, controlliamo l'implementazione. Il Resultnodo vive dentro nodeResult.c, che dice:

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

Il codice è abbastanza semplice.

Function Scanvive in nodeFunctionScan.c, e in effetti sembra prendere una strategia di esecuzione in due fasi :

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

E per chiarezza, vediamo cosa tuplestoreè :

 * 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.

Ipotesi confermata. Function Scanesegue in anticipo, materializzando i risultati della funzione, che per risultati di grandi dimensioni determina la fuoriuscita sul disco. Resultnon materializza nulla, ma supporta anche solo operazioni banali.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.