2

===== aktualisierte Basierend auf dem Feedback =====PostgresSQL CTE/Sub-Abfrage mit generate_series Performance-Probleme

Aufgrund einiger anfänglichen Fragen Klärung suchen, hier ist eine wirklich einfache Version davon.

WITH my_var AS ( 
    SELECT date '2016-01-01' as a_date   
    --, generate_series(1, 40) as numbers  
    ) 
Select generate_series(1, 100000) as numbers, my_var.a_date from my_var 

execution time: 411ms 

"CTE Scan on my_var (cost=0.01..5.03 rows=1000 width=4)" 
" CTE my_var" 
" -> Result (cost=0.00..0.01 rows=1 width=0)" 

Nun, wenn wir die die generate_series im

WITH my_var AS ( 
    SELECT date '2016-01-01' as a_date   
    , generate_series(1, 40) as numbers  
) 
Select generate_series(1, 100000) as numbers, my_var.a_date from my_var 

execution time: 16201ms 

"CTE Scan on my_var (cost=5.01..5022.51 rows=1000000 width=4)" 
" CTE my_var" 
" -> Result (cost=0.00..5.01 rows=1000 width=0)" 

Der Punkt Kommentar- hier zu sein, ist, wenn die generate_series (1, 40) nur einmal ausgeführt werden soll, warum ist es so braucht lang, bis die Abfrage abgeschlossen ist. In diesem Fall habe ich nicht einmal die 'Nummer' verwendet, die in der Hauptabfrage festgelegt wurde, und es dauerte immer noch und die Zeitspanne wurde verlängert.

===== ===== Original-Anfrage

Ich habe in einem interessanten Leistungsproblem führen mit PostgresSQL 9.x mit Unterabfragen und/oder CTE.

... Und ganz ehrlich, ich bin nicht sicher, ob dies ein "Bug" oder nur ein Benutzer (d. H. Ich) Verständnis von CTE/Sub-Abfragen und oder Verwendung der generate_series-Funktion ist.

Ich habe einige erweiterte und längere Abfragen mit CTE geschrieben. Ich habe eine Technik verwendet, bei der ich eine statische Variable wie Datumsangaben in einen Master-CTE eingefügt habe, der alle zusätzlichen Abfragen durchsucht. Die Idee besteht darin, eine Menge von Änderungen anstelle von vielen durch eine lange Abfrage zu machen, wenn Sie sie mit anderen Parametern ausführen müssen.

Ein Beispiel hierfür ist:

WITH dates AS ( 
    SELECT 
     date '2013-01-01' AS start_date, 
     date_trunc('month', current_date) AS end_date 
) 
SELECT * from dates, sometable where somedate between start_date and end_date 

execution time: ~650ms 

Also, es ist mein Verständnis, dass ein CTE einmal ausgeführt wird, aber nach einem Performance-Problem ausgeführt wird, ist dies eindeutig nicht, was geschieht. Zum Beispiel, wenn ich den CTE ändern, um eine generate_series enthalten:

WITH dates AS ( 
    SELECT 
     date '2013-01-01' AS start_date, 
     date_trunc('month', current_date) AS end_date, 
     generate_series(1, 10) AS somelist 
) 
SELECT * from dates, sometable where somedate between start_date and end_date 
    and myval in (somelist) 

execution time: ~23000ms 

Aufgrund einiger ernsthaften Performance-Probleme mit diesem (tausendfach langsamer), an dachte ich zuerst die generate_series() wurde die Zuordnung den somelist der „generate_series "Funktion und wird dann als Unterabfrage für jede Zeile in der Hauptabfrage ausgeführt. So bestätigen diese modifizierte ich die Abfrage lautet wie folgt:

WITH dates AS ( 
    SELECT 
     date '2013-01-01' AS start_date, 
     date_trunc('month', current_date) AS end_date--, 
     --generate_series(1, 10) AS somelist 
) 
SELECT * from dates, sometable where somedate between start_date and end_date 
    and myval in (generate_series(1, 10)) 


execution time: ~700ms 

Zu meiner Überraschung, das relativ schnell war (und nur 10% langsamer). Die generate_series als Unterabfrage ist eindeutig nicht das Problem.

So ging dann auf die ursprüngliche Abfrage zurückgegangen und nur die generate_series hinzugefügt, aber nie in der Hauptabfrage verwendet. Hier ist diese Abfrage.

WITH dates AS ( 
    SELECT 
     date '2013-01-01' AS start_date, 
     date_trunc('month', current_date) AS end_date, 
     generate_series(1, 10) AS somelist 
) 
SELECT * from dates, sometable where somedate between start_date and end_date 

execution time: ~23000ms 

Das ist eindeutig die rauchende Waffe ... aber ich habe keine Ahnung, warum oder was wirklich los ist. Hier sind meine Fragen:

Zusammenfassend verbraucht die Verwendung der generate_series innerhalb einer CTE oder Unterabfrage sehr viel Zeit/Ressourcen (auch wenn das Ergebnis nicht verwendet wird). Ich bekomme die gleichen Ergebnisse in beiden Postgres v9.3 und v9.5. Ich liege gegen 14 Millionen Zeilen. Die Ergebnismenge beträgt nur etwa 275.000.

Ich bin an dieser Stelle ratlos, hat jemand irgendwelche Theorien? (... oder ist es ein Fehler?

)

-D

+0

Interessante Frage, aber Sie müssen mehr Informationen über 'someable' wie Index zur Verfügung stellen. Es wird auch gut sein, wenn Sie die 'explain analyze' aller Abfragen einschließen, damit wir verstehen können, was die rdbms macht und zwischen jeder Abfrage vergleichen. –

+0

Fügen Sie Ihren Abfragen Tabellenaliasse hinzu und verwenden Sie sie, wenn Sie auf Spaltennamen verweisen. Es ist völlig vage, woher der Ausdruck kommt, es könnte sogar ein kartesisches Produkt sein. ad anstelle von 'SELECT * aus Daten, beliebig 'könnte man die JOIN-Syntax für zusätzliche Lesbarkeit verwenden. – wildplasser

+0

@Juan - Mein Punkt ist, dass es nicht wichtig ist, Indizes. Es muss kein Ereignis mit einer realen Tabelle verbunden werden. Ich werde ein Update veröffentlichen, um dies zu zeigen. – user2259963

Antwort

0

Experiment (ich die Daten weggelassen, weil sie nur zusätzliche skalare Konstanten sind)

EXPLAIN 
WITH my_cte_b AS (
     SELECT generate_series(1, 40) as b_number 
     ) 
, my_cte_c AS (
     SELECT generate_series(1, 1000) AS c_number 
     ) 
Select 
     my_cte_b.b_number 
     , my_cte_c.c_number 
FROM my_cte_b 
JOIN my_cte_c ON (1=1) 
     ; 

Ergebnis:

        QUERY PLAN        
------------------------------------------------------------------ 
Nested Loop (cost=5.01..10020.01 rows=1000000 width=8) 
    CTE my_cte_b 
    -> Result (cost=0.00..2.50 rows=1000 width=0) 
    CTE my_cte_c 
    -> Result (cost=0.00..2.50 rows=1000 width=0) 
    -> CTE Scan on my_cte_b (cost=0.00..10.00 rows=1000 width=4) 
    -> CTE Scan on my_cte_c (cost=0.00..10.00 rows=1000 width=4) 
(7 rows) 

Aber EXPLAIN ANALYZE gibt die Korrektes Ergebnis:

----------------------------- 
Nested Loop (cost=5.01..10020.01 rows=1000000 width=8) (actual time=0.029..8.953 rows=40000 loops=1) 
    CTE my_cte_b 
    -> Result (cost=0.00..2.50 rows=1000 width=0) (actual time=0.013..0.019 rows=40 loops=1) 
    CTE my_cte_c 
    -> Result (cost=0.00..2.50 rows=1000 width=0) (actual time=0.002..0.095 rows=1000 loops=1) 
    -> CTE Scan on my_cte_b (cost=0.00..10.00 rows=1000 width=4) (actual time=0.021..0.040 rows=40 loops=1) 
    -> CTE Scan on my_cte_c (cost=0.00..10.00 rows=1000 width=4) (actual time=0.000..0.104 rows=1000 loops=40) 
Planning time: 0.042 ms 
Execution time: 25.206 ms 
(9 rows) 

, so scheint das Problem in der Schätzung zu sein, nicht in der Ausführung.


Als Bonus: Sie können Hinweis (oder: Narr) der Planer durch einen LIMIT xx in der CTEs setzen:


EXPLAIN ANALYZE 
WITH my_cte_b AS (
     SELECT generate_series(1, 40) as b_number 
     LIMIT 40 
     ) 
, my_cte_c AS (
     SELECT generate_series(1, 1000) AS c_number 
     LIMIT 10000 
     ) 
Select 
     my_cte_b.b_number 
     , my_cte_c.c_number 
FROM my_cte_b 
JOIN my_cte_c ON (1=1) 
     ; 

                QUERY PLAN             
---------------------------------------------------------------------------------------------------------------- 
Nested Loop (cost=2.60..408.00 rows=40000 width=8) (actual time=0.019..9.347 rows=40000 loops=1) 
    CTE my_cte_b 
    -> Limit (cost=0.00..0.10 rows=40 width=0) (actual time=0.008..0.018 rows=40 loops=1) 
      -> Result (cost=0.00..2.50 rows=1000 width=0) (actual time=0.006..0.013 rows=40 loops=1) 
    CTE my_cte_c 
    -> Limit (cost=0.00..2.50 rows=1000 width=0) (actual time=0.002..0.241 rows=1000 loops=1) 
      -> Result (cost=0.00..2.50 rows=1000 width=0) (actual time=0.002..0.134 rows=1000 loops=1) 
    -> CTE Scan on my_cte_b (cost=0.00..0.40 rows=40 width=4) (actual time=0.012..0.036 rows=40 loops=1) 
    -> CTE Scan on my_cte_c (cost=0.00..10.00 rows=1000 width=4) (actual time=0.000..0.112 rows=1000 loops=40) 
Planning time: 0.096 ms 
Execution time: 10.693 ms 
(11 rows) 

Mein Fazit: Der Planer hat keine Statistiken über die CTEs (sie enthalten keine Referenzen auf physische Tabellen) und nur eine Vermutung (1000). Diese Schätzung kann außer Kraft gesetzt werden, indem ein LIMIT in die CTEs gesetzt wird.

Verwandte Themen