¿Por qué una función de retorno de conjunto (SRF) se ejecuta más lentamente en una cláusula FROM?

8

Esta es una pregunta de base de datos interna. Estoy usando PostgreSQL 9.5, me pregunto por qué las Funciones de devolución de conjuntos (SRF), también conocidas como Funciones de valor de tabla (TVF), se ejecutan más lentamente cuando están en una FROMcláusula, por ejemplo, cuando ejecuto estos comandos,

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

Es siempre considerablemente más lento que,

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

¿Existe una regla general que se pueda hacer aquí, de tal manera que siempre deberíamos ejecutar Funciones de devolución de conjuntos fuera de una FROMcláusula?

Evan Carroll
fuente

Respuestas:

13

Comencemos comparando los planes de ejecución:

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)

Bien, ahora sabemos que SELECT * FROM generate_series()se ejecuta usando un Function Scannodo, mientras que SELECT generate_series()se ejecuta usando un Resultnodo. Lo que sea que esté causando que estas consultas se realicen de manera diferente se reduce a la diferencia entre estos dos nodos, y sabemos exactamente dónde buscar.

Otra cosa interesante en la EXPLAIN ANALYZEsalida: tenga en cuenta los tiempos. SELECT generate_series()es actual time=0.008..2622.365, mientras SELECT * FROM generate_series()es actual time=2382.582..4291.136. El Function Scannodo comienza a devolver registros alrededor del tiempo en que el Resultnodo terminó de devolver registros.

¿Qué estaba haciendo PostgreSQL entre t=0y t=2382en el Function Scanplan? Aparentemente, se trata de cuánto tiempo lleva correr generate_series(), así que apuesto a que eso es exactamente lo que estaba haciendo. La respuesta comienza a tomar forma: parece que Resultdevuelve resultados de inmediato, mientras que parece Function Scanmaterializar los resultados y luego los escanea.

Con el EXPLAINfuera del camino, vamos a ver la ejecución. El Resultnodo vive en nodeResult.c, que dice:

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

El código es lo suficientemente simple.

Function Scanvive en nodeFunctionScan.c, y de hecho parece tomar una estrategia de ejecución de dos fases :

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

Y para mayor claridad, veamos qué tuplestorees a :

 * 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ótesis confirmada. Function Scanse ejecuta por adelantado, materializando los resultados de la función, que para resultados grandes establece resultados en derrames en el disco. Resultno materializa nada, pero también solo admite operaciones triviales.

willglynn
fuente