Warum sollte eine äußere Verbindung langsamer sein als getrennte Abfragen?

8

Ich habe eine Abfrage, die im Prinzip so aussieht:

%Vor%

Bei bestimmten Werten von @beginDate und @endDate habe ich eine Suche, die 30 Sekunden dauert, um ungefähr 100 K Zeilen zurückzugeben.

Das ultimative Ziel besteht darin, einige Objekte mit Eltern-Kind-Kind-Kind-Beziehungen zu füllen. Nach einigen Experimenten stellte ich fest, dass ich die Abfrage dramatisch beschleunigen konnte:

%Vor%

Dies läuft in 10 Sekunden, was für mich keinen Sinn ergibt. Sicherlich sollte es schneller sein, nur um den Zuschüssen an erster Stelle beizutreten.

Die Zuschlagstabelle hat die folgenden Indizes:

PK:

%Vor%

IX1:

%Vor%

IX2:

%Vor%

Zusammenfassend: Warum ist es schneller, eine separate Abfrage für meine Surcharges durchzuführen, als sie zuerst zu verbinden?

BEARBEITEN : Hier sind die Ausführungspläne. Dies sind .sqlplan-Dateien, die Sie in Sql Studio öffnen können:

Abfrage 1 - Kombiniert

Abfrage 2 - Trennen

    
Pharylon 14.09.2015, 14:09
quelle

4 Antworten

8

Um zu verstehen, was vor sich geht, schauen Sie sich die tatsächlichen Ausführungspläne an.

Vorzugsweise in SQL Sentry-Plan-Explorer .

Sie werden sehen, dass Ihre erste Variante Actual Data Size = 11.272 MB in 100.276 Zeilen hat.

In der zweiten Variante gibt die Abfrage, die die temporäre Tabelle auffüllt, in 19.665 Zeilen nur 173 KB zurück. Die letzte Abfrage gibt 1.685 MB in 87.510 Zeilen zurück.

11,272 MB ist viel mehr als 1,685 MB

Kein Wunder, dass die erste Abfrage langsamer ist.

Dieser Unterschied wird durch zwei Faktoren verursacht:

  1. In der ersten Variante wählen Sie alle Spalten aus UserSearches , Quotes , ContainerDetails Tabellen aus. Während in der zweiten Variante wählen Sie nur ID von ContainerDetails . Abgesehen von dem Lesen von der Platte und dem Übertragen über zusätzliche Netzwerkbytes führt diese Differenz zu wesentlich unterschiedlichen Plänen. Die zweite Variante führt keine Sortierung durch, führt keine Schlüsselsuche durch und verwendet Hash-Joins anstelle von verschachtelten Schleifen. Es verwendet verschiedene Indizes für Quotes . Die zweite Variante verwendet den Index Scan auf ContainerDetails anstelle von Seek.

  2. Die Abfragen erzeugen eine unterschiedliche Anzahl von Zeilen, weil die erste Variante LEFT JOIN und die zweite INNER JOIN verwendet.

Also, um sie vergleichbar zu machen:

  1. Anstatt * explizit zu verwenden, listen Sie nur die Spalten auf, die Sie benötigen.
  2. Verwenden Sie INNER JOIN (oder LEFT JOIN ) Surcharges in beiden Varianten.

update

Ihre Frage lautete: "Warum führt SQL Server die zweite Abfrage schneller aus?" lautet die Antwort: weil die Abfragen unterschiedlich sind und unterschiedliche Ergebnisse liefern (unterschiedliche Zeilen, unterschiedliche Spalten).

Jetzt stellst du eine andere Frage: wie man sie gleich und schnell macht.

Welche Ihrer beiden Varianten liefert das richtige Ergebnis? Ich nehme an, dass es die zweite Variante mit Temp-Tabelle ist.

Bitte beachtet, dass ich hier nicht antworte, wie man sie schnell macht. Ich antworte hier, wie man sie gleich macht.

Die folgende einzelne Abfrage sollte genau die gleichen Ergebnisse liefern wie Ihre zweite Variante mit temporärer Tabelle, aber ohne explizite temporäre Tabelle. Ich würde erwarten, dass seine Leistung der zweiten Variante mit temporären Tabellen ähnelt. Ich habe es bewusst geschrieben mit CTE, um die Struktur Ihrer Variante mit Temp-Tabelle zu kopieren, obwohl es leicht ist, ohne es umzuschreiben. Der Optimierer wäre schlau genug, es trotzdem zu tun.

%Vor%     
Vladimir Baranov 17.09.2015 03:42
quelle
3

Nun, es scheint zwei große beitragende Unterschiede zu geben, was ich in den Abfragen selbst sowie in den Plänen sehen kann.

Zuerst und wahrscheinlich am wirkungsvollsten ist in Ihrer zweiten Version, in der Sie die temporäre Tabelle verwenden, Ihre letzte Abfrage gegen die Surcharges-Tabelle INNER JOINing anstelle des LEFT JOIN-Operators, den Sie in der ursprünglichen Abfrage verwendet haben. Ich bin nicht sicher, welche Version korrekt ist, aber der Unterschied in der Anzahl der zurückgegebenen Datensätze scheint sehr hoch zu sein, basierend auf den Planinformationen (18,6 Millionen in der ersten Version gegenüber 5,1 Millionen in der zweiten Version). Wenn Sie Ihre erste Version zu einem INNER JOIN in der Tabelle "Zuschläge" ändern, sehen Sie ähnliche Ergebnisse in Bezug auf die Dauer?

Zweitens, und wahrscheinlich weniger wirkungsvoll, gibt Ihnen Ihre zweite Version eine parallele Ausführung, die auf dem ausgewählten Teil des Stapels erscheint. Ohne weitere Dinge zu sehen, würde ich es wahrscheinlich nicht wagen zu kommentieren, warum das so ist, aber es ist ein potenzielles Unterscheidungsmerkmal.

Ich würde mit dem ersten Mitwirkenden beginnen und sehen, womit Sie fertig sind und von dort fortgehen.

BEARBEITEN:

Um zu helfen, mit den Kommentaren zu klären, versuchen Sie, Ihre erste Abfrage zu diesem zu ändern und den Abfrageplan anzufügen / die Ergebnisse / Dauer davon gegenüber der temporären Tabelle zu überprüfen / select ... in version:

%Vor%

Das sollte Ihnen hoffentlich mehr oder weniger ähnliche Dauer geben wie die zweite Version - wenn noch nicht, fügen Sie bitte den Abfrageplan für diese Version bei.

    
chadhoc 16.09.2015 22:04
quelle
1

Aber Sie vergleichen nicht Äpfel mit Äpfeln Die erste ist 3 übrig Der zweite ist 2 links und 1 innerer beitreten Und in der Sekunde werden die Ergebnisse geteilt

Probieren Sie dieses
aus Verschiebe us.SearchDate zwischen @beginDate und @endDate in die Join-Datei Ich vermute, dass es eine massive Verbindung macht und die letzten Filterung Rufen Sie den Datumsfilter früh auf

%Vor%

Die schnelle Suche ergibt für mich keinen Sinn

Diese linken Joins machen absolut nichts zu diesem
Alles was übrig ist, ist cd.ID = null

%Vor%

Wenn Sie nur Surcharges wollen, dann

%Vor%     
paparazzo 16.09.2015 22:24
quelle
0
  1. Haben Sie einmal versucht, & lt; = und & gt; = statt zwischen.
  2. zu verwenden
  3. Erstellen Sie auch einen nicht gruppierten Index für SearchDate.
  4. Deklarieren Sie auch lokale Variable in Ihrem Prozess, um parameter sniffing zu vermeiden.
  5. Geben Sie anstelle von * die Spalte an, die Sie benötigen.
  6. Sind Sie sicher, dass linker Join anstelle von innerem Join in Ordnung ist.

wie in deinem Prozess, deklariere @ beginDate1 datetime = @ beginDate deklariere @ endDate1 datetime = @ endDate

versuche das,

%Vor%     
KumarHarsh 19.09.2015 09:56
quelle