Давайте начнем со сравнения планов выполнения:
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)
Итак, теперь мы знаем, что SELECT * FROM generate_series()
выполняется с использованием Function Scan
узла, а SELECT generate_series()
выполняется с использованием Result
узла. То, что вызывает выполнение этих запросов по-разному, сводится к разнице между этими двумя узлами, и мы точно знаем, где искать.
Еще одна интересная вещь в EXPLAIN ANALYZE
выводе: обратите внимание на сроки. SELECT generate_series()
есть actual time=0.008..2622.365
, пока SELECT * FROM generate_series()
есть actual time=2382.582..4291.136
. Function Scan
Узел начинает возвращения записи по времени Result
узел закончил возвращение записей.
Что делал PostgreSQL между планом t=0
и t=2382
в Function Scan
плане? По-видимому, это примерно столько времени, сколько нужно для бега generate_series()
, поэтому я бы поспорил, что это именно то, что он делал. Ответ начинает обретать форму: кажется, что Result
возвращает результаты немедленно, в то время как кажется, что Function Scan
материализует результаты, а затем сканирует их.
Вне EXPLAIN
пути, давайте проверим реализацию. Result
Узел живет nodeResult.c
, в котором говорится:
* DESCRIPTION
*
* Result nodes are used in queries where no relations are scanned.
Код достаточно прост.
Function Scan
живет nodeFunctionScan.c
, и действительно, кажется, принимает двухфазную стратегию исполнения :
/*
* If first time through, read all tuples from function and put them
* in a tuplestore. Subsequent calls just fetch tuples from
* tuplestore.
*/
А для наглядности, давайте посмотрим , что такое 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.
Гипотеза подтвердилась. Function Scan
выполняется заранее, материализуя результаты функции, что для больших наборов результатов приводит к разливу на диск. Result
ничего не материализует, но поддерживает только тривиальные операции.