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 Scan
nodo, mentre SELECT generate_series()
viene eseguito utilizzando un Result
nodo. 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 ANALYZE
nell'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 Scan
nodo inizia a restituire i record nel momento in cui il Result
nodo ha finito di restituire i record.
Cosa stava facendo PostgreSQL tra t=0
e t=2382
nel Function Scan
piano? 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 Result
restituisca immediatamente i risultati, mentre sembra Function Scan
materializzare i risultati e quindi scansionarli.
Con il EXPLAIN
fuori mano, controlliamo l'implementazione. Il Result
nodo vive dentro nodeResult.c
, che dice:
* DESCRIPTION
*
* Result nodes are used in queries where no relations are scanned.
Il codice è abbastanza semplice.
Function Scan
vive 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 Scan
esegue in anticipo, materializzando i risultati della funzione, che per risultati di grandi dimensioni determina la fuoriuscita sul disco. Result
non materializza nulla, ma supporta anche solo operazioni banali.