Pourquoi une fonction de retour définie (SRF) s'exécute-t-elle plus lentement dans une clause FROM?


8

Ceci est une question de base de données interne. J'utilise PostgreSQL 9.5, je me demande pourquoi Set Returning Functions (SRF), également connu sous le nom de Table Valued Functions (TVF) fonctionne plus lentement dans une FROMclause, par exemple lorsque j'exécute ces commandes,

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

C'est toujours beaucoup plus lent que,

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

Y a-t-il une règle générale qui peut être établie ici, de sorte que nous devrions toujours exécuter des fonctions de retour de set en dehors d'une FROMclause?

Réponses:


13

Commençons par comparer les plans d'exécution:

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)

D'accord, nous savons maintenant que cela SELECT * FROM generate_series()est exécuté à l'aide d'un Function Scannœud, tandis que cela SELECT generate_series()est exécuté à l'aide d'un Resultnœud. Tout ce qui fait que ces requêtes s'exécutent différemment se résume à la différence entre ces deux nœuds, et nous savons exactement où chercher.

Une autre chose intéressante dans la EXPLAIN ANALYZEsortie: notez les timings. SELECT generate_series()est actual time=0.008..2622.365, tout SELECT * FROM generate_series()est actual time=2382.582..4291.136. Le Function Scannœud commence à renvoyer des enregistrements au moment où le Resultnœud a fini de renvoyer des enregistrements.

Que faisait PostgreSQL entre t=0et t=2382dans le Function Scanplan? Apparemment, c'est à peu près le temps qu'il faut pour courir generate_series(), alors je parierais que c'est exactement ce qu'il faisait. La réponse commence à prendre forme: il semble que les résultats soient Resultretournés immédiatement, alors qu'ils semblent Function Scanmatérialiser les résultats, puis les numériser.

Maintenant que nous EXPLAINsommes à l'écart, vérifions l'implémentation. Le Resultnœud habite nodeResult.c, ce qui dit:

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

Le code est assez simple.

Function Scanvit nodeFunctionScan.c, et en effet il semble prendre une stratégie d'exécution en deux phases :

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

Et pour plus de clarté, nous allons voir quel tuplestoreest :

 * 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.

Hypothèse confirmée. Function Scans'exécute en amont, matérialisant les résultats de la fonction, ce qui, pour les jeux de résultats volumineux, entraîne un débordement sur le disque. Resultne matérialise rien, mais prend également en charge uniquement les opérations triviales.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.