Por que uma SRF (Set Returning Function) é executada mais lentamente na cláusula FROM?

8

Esta é uma pergunta interna do banco de dados. Estou usando o PostgreSQL 9.5. Estou me perguntando por que SRFs (Set Returning Functions), também conhecidas como TVFs (Table Valued Functions), são mais lentas quando em uma FROMcláusula, por exemplo, quando executo esses comandos,

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

É sempre substancialmente mais lento que,

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

Existe uma regra geral que pode ser feita aqui, de modo que devemos sempre executar funções de retorno de conjunto fora de uma FROMcláusula?

Evan Carroll
fonte

Respostas:

13

Vamos começar comparando os planos de execução:

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)

Ok, agora sabemos que SELECT * FROM generate_series()é executado usando um Function Scannó, enquanto SELECT generate_series()é executado usando um Resultnó. O que quer que esteja causando o desempenho dessas consultas se resume à diferença entre esses dois nós e sabemos exatamente onde procurar.

Outra coisa interessante na EXPLAIN ANALYZEsaída: observe os tempos. SELECT generate_series()é actual time=0.008..2622.365, enquanto SELECT * FROM generate_series()é actual time=2382.582..4291.136. O Function Scancomeça a retornar registros em todo o tempo que o Resultterminou retornando registros.

O que o PostgreSQL estava fazendo entre t=0e t=2382no Function Scanplano? Aparentemente, é sobre quanto tempo leva para correr generate_series(), então eu aposto que é exatamente o que estava fazendo. A resposta começa a tomar forma: parece que Resultretorna resultados imediatamente, enquanto parece Function Scanmaterializar os resultados e depois varrê-los.

Com o EXPLAINfora do caminho, vamos verificar a implementação. O Resultnó mora nodeResult.c, que diz:

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

O código é bastante simples.

Function Scanvive nodeFunctionScan.ce, de fato, parece ter uma estratégia de execução em duas fases :

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

E para maior clareza, vamos ver o que 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.

Hipótese confirmada. Function Scanexecuta antecipadamente, materializando os resultados da função, que, para grandes resultados, resulta em derramamento no disco. Resultnão materializa nada, mas também suporta apenas operações triviais.

willglynn
fonte