Почему функция возврата набора (SRF) работает медленнее в предложении FROM?

8

Это вопрос внутренней базы данных. Я использую PostgreSQL 9.5, мне интересно, почему Set Returning Functions (SRF), также известные как Table-Valued Functions (TVFs), работают медленнее, когда в FROMпредложении, например, когда я выполняю эти команды,

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

Это всегда существенно медленнее, чем,

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

Есть ли здесь общее правило, такое, что мы всегда должны запускать функции возврата-набора вне FROMпредложения?

Эван Кэрролл
источник

Ответы:

13

Давайте начнем со сравнения планов выполнения:

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ничего не материализует, но поддерживает только тривиальные операции.

willglynn
источник