Gibt es einen Leistungsunterschied zwischen CTE, Unterabfrage, temporärer Tabelle oder Tabellenvariable?


222

In dieser ausgezeichneten SO-Frage wurden Unterschiede zwischen CTEund sub-queriesdiskutiert.

Ich möchte speziell fragen:

Unter welchen Umständen ist jeder der folgenden Punkte effizienter / schneller?

  • CTE
  • Unterabfrage
  • Temporäre Tabelle
  • Tabellenvariable

Traditionell habe ich viel für die temp tablesEntwicklung verwendet stored procedures- da sie lesbarer zu sein scheinen als viele miteinander verflochtene Unterabfragen.

Non-recursive CTEs kapseln Datensätze sehr gut und sind sehr gut lesbar. Gibt es jedoch bestimmte Umstände, unter denen man sagen kann, dass sie immer eine bessere Leistung erbringen? oder muss man immer mit den verschiedenen Optionen herumspielen, um die effizienteste Lösung zu finden?


BEARBEITEN

Mir wurde kürzlich gesagt, dass temporäre Tabellen in Bezug auf die Effizienz eine gute erste Wahl sind, da ihnen ein Histogramm zugeordnet ist, dh Statistiken.


4
Allgemeine Antwort: es kommt darauf an. Und es hängt von vielen Faktoren ab, jede allgemeine Aussage ist wahrscheinlich falsch - in einigen Situationen. Grundsätzlich gilt: Sie müssen testen und messen - finden Sie heraus, welche für Sie am besten geeignet ist!
marc_s

@marc_s - ok; Vielleicht sollte diese Frage geschlossen werden, um subjektiv zu sein? Wohlgemerkt, viele SQL-Fragen zu SO könnten als subjektiv beurteilt werden.
Whytheq

1
Es könnte geschlossen werden, weil es zu weit gefasst ist - und ich stimme Ihnen zu -, viele Dinge und Themen in SQL werden wirklich eine Antwort darauf bekommen, hängt davon ab . Manchmal kann man zwei oder drei Kriterien auflisten, um eine Entscheidung zu treffen, aber bei Ihrer Frage hier ist es nahezu unmöglich, fundierte Ratschläge zu geben - es hängt so sehr davon ab - Ihre Tabellenstrukturen, Daten in diesen Tabellen, Abfragen, die Sie verwenden, Ihre Indizierungsstrategie und vieles mehr ....
marc_s

@marc_s wäre es gut zu versuchen und zu behalten - irgendwelche Ratschläge zu möglichen Änderungen an OP, um zu versuchen, es spezifischer und enger zu machen?
Whytheq

Bitte beachten Sie, dass diese Frage spezifisch für SQL Server ist. Für andere DBs wie Postgres ist ein CTE oft viel langsamer als gleichwertige Unterabfragen (siehe http://blog.2ndquadrant.com/postgresql-ctes-are-optimization-fences/ )
Jay

Antworten:


243

SQL ist eine deklarative Sprache, keine prozedurale Sprache. Das heißt, Sie erstellen eine SQL-Anweisung, um die gewünschten Ergebnisse zu beschreiben. Sie sagen der SQL-Engine nicht, wie sie die Arbeit erledigen soll.

In der Regel empfiehlt es sich, die SQL-Engine und den SQL-Optimierer den besten Abfrageplan finden zu lassen. Die Entwicklung einer SQL-Engine erfordert viele jahrelange Anstrengungen. Lassen Sie die Ingenieure also tun, was sie können.

Natürlich gibt es Situationen, in denen der Abfrageplan nicht optimal ist. Anschließend möchten Sie Abfragehinweise verwenden, die Abfrage umstrukturieren, Statistiken aktualisieren, temporäre Tabellen verwenden, Indizes hinzufügen usw., um eine bessere Leistung zu erzielen.

Wie für Ihre Frage. Die Leistung von CTEs und Unterabfragen sollte theoretisch gleich sein, da beide dem Abfrageoptimierer dieselben Informationen bereitstellen. Ein Unterschied besteht darin, dass ein mehr als einmal verwendeter CTE leicht identifiziert und einmal berechnet werden kann. Die Ergebnisse könnten dann mehrmals gespeichert und gelesen werden. Leider scheint SQL Server diese grundlegende Optimierungsmethode nicht zu nutzen (Sie können diese allgemeine Eliminierung von Unterabfragen nennen).

Temporäre Tabellen sind eine andere Sache, da Sie mehr Anleitungen zur Ausführung der Abfrage geben. Ein wesentlicher Unterschied besteht darin, dass das Optimierungsprogramm Statistiken aus der temporären Tabelle verwenden kann, um seinen Abfrageplan zu erstellen. Dies kann zu Leistungssteigerungen führen. Wenn Sie einen komplizierten CTE (Unterabfrage) haben, der mehrmals verwendet wird, führt das Speichern in einer temporären Tabelle häufig zu einer Leistungssteigerung. Die Abfrage wird nur einmal ausgeführt.

Die Antwort auf Ihre Frage lautet, dass Sie herumspielen müssen, um die erwartete Leistung zu erzielen, insbesondere bei komplexen Abfragen, die regelmäßig ausgeführt werden. In einer idealen Welt würde der Abfrageoptimierer den perfekten Ausführungspfad finden. Obwohl dies häufig der Fall ist, können Sie möglicherweise einen Weg finden, um eine bessere Leistung zu erzielen.


11
Einige Microsoft-Untersuchungen zu möglichen zukünftigen Verbesserungen in diesem Bereich finden Sie in der Veröffentlichung "Effiziente Nutzung ähnlicher Unterausdrücke für die Abfrageverarbeitung", die hier verfügbar ist
Martin Smith,

3
Gibt es angesichts der Tatsache, dass dieses Papier 2007 vorgestellt wurde, eine Idee, ob es in SQL Server 2012 integriert wurde?
Gordon Linoff

3
Eine gute Antwort! Nur um zu betonen: SQL ist eine deklarative Sprache, und wir steuern nicht, wie die Daten abgerufen werden. Daher variiert die Leistung / Geschwindigkeit von Abfrage zu Abfrage.
Simcha Khabinsky

2
@RGS. . . Indizes für temporäre Tabellen verbessern definitiv Abfragen, die diese Indizes nutzen können - wie bei Indizes für eine permanente Tabelle. Wenn Sie jedoch eine Unterabfrage als temporäre Tabelle materialisieren, verlieren Sie möglicherweise den Vorteil der Indizes für die ursprünglichen Tabellen.
Gordon Linoff

2
@RGS. . Wenn ein Datenbankmodul während der Ausführung einer komplexen Abfrage eine Unterabfrage / einen CTE materialisiert, werden keine Indizes zur Materialisierung hinzugefügt. Sie können dies manuell mithilfe temporärer Tabellen tun.
Gordon Linoff

77

Es gibt keine Regel. Ich finde CTEs besser lesbar und verwende sie, es sei denn, sie weisen Leistungsprobleme auf. In diesem Fall untersuche ich das eigentliche Problem, anstatt zu vermuten, dass der CTE das Problem ist, und versuche, es mit einem anderen Ansatz neu zu schreiben. Das Problem hat normalerweise mehr zu bieten als die Art und Weise, wie ich meine Absichten mit der Abfrage deklarativ dargelegt habe.

Es gibt sicherlich Fälle, in denen Sie CTEs entwirren oder Unterabfragen entfernen und durch eine # temp-Tabelle ersetzen und die Dauer verkürzen können. Dies kann auf verschiedene Dinge zurückzuführen sein, wie z. B. veraltete Statistiken, die Unfähigkeit, sogar genaue Statistiken zu erhalten (z. B. das Verknüpfen mit einer Tabellenwertfunktion), Parallelität oder sogar die Unfähigkeit, aufgrund der Komplexität der Abfrage einen optimalen Plan zu generieren ( In diesem Fall kann das Aufbrechen dem Optimierer eine Kampfchance geben. Es gibt jedoch auch Fälle, in denen die mit der Erstellung einer # temp-Tabelle verbundenen E / A die anderen Leistungsaspekte überwiegen können, die eine bestimmte Planform mit einem CTE weniger attraktiv machen können.

Ganz ehrlich, es gibt viel zu viele Variablen, um eine "richtige" Antwort auf Ihre Frage zu geben. Es gibt keinen vorhersehbaren Weg zu wissen, wann eine Abfrage zugunsten des einen oder anderen Ansatzes kippen könnte - wissen Sie nur, dass theoretisch dieselbe Semantik für einen CTE oder eine einzelne Unterabfrage genau dieselbe ausführen sollte . Ich denke, Ihre Frage wäre wertvoller, wenn Sie einige Fälle präsentieren, in denen dies nicht zutrifft - es kann sein, dass Sie eine Einschränkung im Optimierer entdeckt haben (oder eine bekannte entdeckt haben), oder dass Ihre Abfragen nicht semantisch äquivalent sind oder dass eines ein Element enthält, das die Optimierung verhindert.

Daher würde ich vorschlagen, die Abfrage so zu schreiben, wie es Ihnen am natürlichsten erscheint, und nur dann abzuweichen, wenn Sie ein tatsächliches Leistungsproblem des Optimierers feststellen. Persönlich stufe ich sie als CTE und dann als Unterabfrage ein, wobei die Tabelle #temp das letzte Mittel ist.


4
+1 stellt sich als ziemlich subjektive Frage heraus; Ich hoffe, es wird nicht geschlossen, weil es zu vage ist, da die bisherigen Antworten informativ sind. Mir ist klar :-) Sie mögen es nicht, wenn sich Fragen ändern, aber haben Sie Vorschläge, um die Frage im OP einzugrenzen?
Whytheq

2
Ich denke, diese Frage ist in Ordnung, Sie werden feststellen, dass es noch keine einzige Abstimmung zum Abschluss gibt, aber wenn die Antworten wild herumwirbeln, wird sie wahrscheinlich geschlossen. Wie ich in meiner Antwort vorgeschlagen habe, beginnen Sie eine neue Frage mit den tatsächlichen Abfragen und Ausführungsplänen , wenn Sie in einem bestimmten Fall einen großen Unterschied zwischen einem CTE und einer Unterabfrage feststellen (und dies passt möglicherweise besser zu dba.se ). . Beachten Sie nur, dass die Antwort zur Unterstützung dieser Abfrage möglicherweise nicht dieselbe Antwort für eine andere Abfrage mit demselben Szenario ist.
Aaron Bertrand

Direkt unter Ihrer Frage befinden sich Links. link / edit / close / flagWenn zum Schließen der Frage Stimmen abgegeben wurden, sehen Sie, close (n)wo ndie Anzahl der Benutzer steht, die zum Schließen Ihrer Frage abgestimmt haben. Wenn Sie auf den Link klicken, werden die Gründe angezeigt, aus denen diese Benutzer ausgewählt wurden.
Aaron Bertrand

@whytheq siehe auch diesen aktuellen Blog-Beitrag von Bob Beauchemin . CTE vs. Unterabfrage wird nicht speziell behandelt, aber es gilt das gleiche Konzept: Wenn Sie aus Leistungsgründen ein nicht intuitives Muster auswählen, dokumentieren Sie den Mist daraus und besuchen Sie es erneut, um sicherzustellen, dass die von Ihnen entdeckte Eigenart immer noch real ist. Ich könnte sogar vorschlagen, die natürlichere Version der Abfrage auskommentiert zu lassen, es sei denn, Sie verfügen über ein zuverlässiges Versionsverwaltungssystem, das die vorherige Version enthält.
Aaron Bertrand

1
Link oben behoben
ADJenks

19

#temp ist materalisiert und CTE nicht.

CTE ist nur eine Syntax, also theoretisch nur eine Unterabfrage. Es wird ausgeführt. #temp ist materialisiert. Ein teurer CTE in einem Join, der viele Male ausgeführt wird, ist in einem #temp möglicherweise besser. Auf der anderen Seite, wenn es sich um eine einfache Auswertung handelt, die nicht ausgeführt wird, aber einige Male, dann ist der Overhead von #temp nicht wert.

Es gibt einige Leute auf SO, die keine Tabellenvariablen mögen, aber ich mag sie, da sie materialisiert und schneller zu erstellen sind als #temp. Es gibt Zeiten, in denen das Abfrageoptimierungsprogramm mit einem #temp im Vergleich zu einer Tabellenvariablen besser abschneidet.

Die Möglichkeit, eine PK für eine # temp- oder Tabellenvariable zu erstellen, bietet dem Abfrageoptimierer mehr Informationen als ein CTE (da Sie keine PK für einen CTE deklarieren können).


Was ist das Akronym "TVP" ... ähnlich wie #temp?
Whytheq

TVP wird zu einem gebräuchlichen Begriff, weil es (für einige) beeindruckend klingt. Kurz gesagt, ein TVP ist eine Tabelle, die als Parameter übergeben wird. Jeder, der Tabellenvariablen verwendet hat, ist bei ihnen zu Hause.
WonderWorker

1
WARNUNG - TVPs haben keine Ausführungspläne! Verwenden Sie TVPs nicht für andere als die einfachsten kurzen Suchlisten. Wenn Sie komplexe Verknüpfungen, Einfügungen oder Aktualisierungen vornehmen, können massive Optimierungsprobleme auftreten. Vertrau mir, ich bin davon verbrannt worden.
Heliac

12

Nur zwei Dinge, die es meiner Meinung nach IMMER vorzuziehen machen, eine # Temp-Tabelle anstelle eines CTE zu verwenden, sind:

  1. Sie können einem CTE keinen Primärschlüssel zuweisen, sodass die Daten, auf die der CTE zugreift, jeden der Indizes in den Tabellen des CTE durchlaufen müssen, anstatt nur auf die PK oder den Index in der temporären Tabelle zuzugreifen.

  2. Da Sie einem CTE keine Einschränkungen, Indizes und Primärschlüssel hinzufügen können, sind diese anfälliger für eingeschlichene Fehler und fehlerhafte Daten.


-onedaywhen gestern

Hier ist ein Beispiel, in dem # table-Einschränkungen fehlerhafte Daten verhindern können, was bei CTEs nicht der Fall ist

DECLARE @BadData TABLE ( 
                       ThisID int
                     , ThatID int );
INSERT INTO @BadData
       ( ThisID
       , ThatID
       ) 
VALUES
       ( 1, 1 ),
       ( 1, 2 ),
       ( 2, 2 ),
       ( 1, 1 );

IF OBJECT_ID('tempdb..#This') IS NOT NULL
    DROP TABLE #This;
CREATE TABLE #This ( 
             ThisID int NOT NULL
           , ThatID int NOT NULL
                        UNIQUE(ThisID, ThatID) );
INSERT INTO #This
SELECT * FROM @BadData;
WITH This_CTE
     AS (SELECT *
           FROM @BadData)
     SELECT *
       FROM This_CTE;

3
ALWAYSist etwas zu weit aber danke für die antwort. In Bezug auf die Lesbarkeit kann die Verwendung von CTEs eine gute Sache sein.
Whytheq

3
Ich verstehe Ihren zweiten Punkt überhaupt nicht. So wie ich es sehe, ist die Abfrage, die den CTE definiert, analog zu den Einschränkungen, die Sie für die temporäre Tabelle festlegen würden, wobei zu beachten ist, dass die erstere beliebig komplexe Prädikate umfassen kann, während die letztere weitaus eingeschränkter ist (z. B. eine CHECKEinschränkung, die sich auf mehrere Zeilen / Tabellen bezieht) nicht erlaubt). Können Sie ein Beispiel veröffentlichen, bei dem ein CTE einen Fehler aufweist, den das temporäre Tabellenäquivalent nicht aufweist?
Eines Tages, wenn
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.