it-swarm.com.de

Warum ist diese linke Verknüpfung schneller als eine innere Verknüpfung?

Ich optimiere diese Abfrage und bin mir ziemlich sicher, dass ich in diesem Fall einen inneren Join durch einen linken Join ersetzen kann, ohne die Daten zu beeinflussen. Ich bin mir jedoch nicht ganz sicher, warum dies schneller ist. Hier ist die Abfrage:

SELECT DISTINCT  cl.NAME                            AS company_name,
                     cl.id                              AS company_id,
                     ep.Plan__c                           AS plan_id,
                     ep.Employee__c,
                     ep.id,
                     do.Subcategory__c,
                     ep.Plan_Type__c,
                     pt.SubType,
                     Sum((pt.[shares] * fvh.[ValuePerShare])) AS TotalValue,
                     ppe.Deferral_Option__c,
                     dt.Defer_type_Code,
                     do.Short_Code__c,
                     pt.ContributionYear
  FROM   dbo.ParticipantTrades pt WITH (NOLOCK) 
        INNER JOIN dbo.PayoutPathElection ppe WITH (NOLOCK) 
                ON pt.payoutPathElectionID = ppe.Id 
        INNER JOIN dbo.DeferralOption do WITH (NOLOCK) 
                ON ppe.Deferral_Option__c = do.id 
        INNER JOIN dbo.EmployeePlan ep WITH (NOLOCK) 
                ON pt.employeePlan = ep.Id 
        LEFT JOIN dbo.DeferralType dt WITH (NOLOCK) 
                ON pt.deferralType = dt.defID
        INNER JOIN dbo.Fnc_lastfundvalue('2019-01-30') AS fvh 
                ON pt.fund = fvh.Fund
        INNER JOIN dbo.Clients cl with (NOLOCK)
                ON ep.Company__c = cl.Id
  WHERE ep.Company__c = '0017000001WL1HfAAL' AND ep.Plan_Type__c  LIKE '%' AND pt.tradeDate <= '2019-01-30'
  Group by   cl.NAME,
             cl.id,
             ep.Plan__c,
             ep.Employee__c,
             ep.id,
             do.Subcategory__c,
             ep.Plan_Type__c,
             pt.SubType,
             ppe.Deferral_Option__c,
             dt.Defer_type_Code,
             do.Short_Code__c,
             pt.ContributionYear

Der Engpass liegt bei der Verknüpfung der Tabellenwertfunktion (Fnc_lastfundvalue). Meine Vermutung, warum das Ändern in einen linken Join schneller ist, ist, dass es dann die Joins neu anordnen kann und weniger Verschüttung in Tempdb verursacht? Hier ist der Abfrageplan vor und nach dem Ändern von INNER JOIN dbo.Fnc_lastfundvalue .. in LEFT JOIN dbo.Fnc_lastfundvalue ..

Vorher (29 Sekunden): https://www.brentozar.com/pastetheplan/?id=Bk1Y-WqEE

Nach (3 Sekunden): https://www.brentozar.com/pastetheplan/?id=B1hoW-544

NB: Die obigen Ausführungspläne werden auf einer Entwicklungsbox erstellt. Der Produktionsserver befindet sich noch unter SQL Server 2008.

4
Rick

Es sieht so aus, als ob ein Teil des Unterschieds darin besteht, dass der langsame Plan zuerst ausgeführt wurde.

Der langsame Plan arbeitete eindeutig gegen einen kalten Cache, da er zusätzliche physische Lesevorgänge anzeigt und im Vergleich zum schnellen Fall zusätzliche 15 Sekunden PAGEIOLATCH_SH Wartezeiten akkumuliert.

Dies sieht so aus, als ob es sowohl die Ausführung der TVF selbst (die im schnellen Fall 5.5 Sekunden im Vergleich zu 1.35 Sekunden dauerte) als auch den umfassenderen Plan, der das Ergebnis verwendet, beeinflusst hat.

Sie sagen in den Kommentaren, dass es bei der zweiten Ausführung 11 Sekunden gedauert hat. Dies ist immer noch dreimal langsamer als der schnelle Plan (3.446 Sekunden), erklärt also nicht alle Leistungsunterschiede.

Das Hauptproblem, das auftritt, ist auf die schlechte Standardschätzung für TVFs mit mehreren Anweisungen zurückzuführen (feste Schätzung von 100 - Zeilen in den Kompatibilitätsstufen 2014/2016 und 1 In früheren Versionen). In Wirklichkeit gibt Ihr TVF 1,715 Zeilen zurück.

Im Fall der inneren Verknüpfung können innere Verknüpfungen, da sie assoziativ und kommutativ sind, flexibel neu angeordnet werden und werden in den tiefsten Teil des Baums verschoben. Dies wäre sinnvoll, wenn eine Zeile tatsächlich zurückgegeben würde, da dies die Anzahl der Zeilen für die anderen Verknüpfungen im Plan frühzeitig verringern könnte.

Der Ausführungsplan ist unten. Die rosa Anmerkungen sind "Tatsächliche verstrichene Zeit (ms)" aus dem XML.

(enter image description here

Aufgrund der Schätzung von 1 Zeile beginnt es schlecht, wenn Sie sich dbo.ParticipantTrades Verbinden und einen Plan mit verschachtelten Schleifen und Suchvorgängen auswählen. Diese verschachtelten Schleifen haben eine verstrichene Zeit von 13.976 Sekunden (vermutlich wurde der größte Teil damit verbracht, auf die verschüttete Sortierung unmittelbar vorgelagert zu warten, um Zeilen von ihr anzufordern, wobei 3.26 Sekunden für die Suchvorgänge selbst und verwendet wurden Associated IO wartet auf die physischen Lesevorgänge bei diesem Operator).

923,646 Zeilen werden vom Join im Vergleich zu einem geschätzten 1423.18 Ausgegeben, und diese Fehleinschätzung breitet sich im Plan durch 3 Sortierungen nach oben aus, und ein Hash-Join wird im Laufe der Zeit verschüttet (aufgrund der unterschätzten Zeile).

Wenn Sie den LEFT JOIN Hinzufügen, kann er nicht so frei nachbestellt werden, und der Join erfolgt viel weiter oben im Plan (wo er im problematischen Fall INNER JOIN Weniger Schaden anrichten würde). Die Semantik der äußeren Verknüpfung hilft hier sowieso.

Während die Schätzung für die Anzahl der Zeilen, die aus der TVF kommen, immer noch 1 Ist, wirkt sich dies nicht nachteilig auf die Schätzungen für den Join aus, an dem sie direkt beteiligt sind (SQL Server geht davon aus, dass die Kardinalität, die aus dieser Verknüpfung kommt, sein wird Das gleiche wie das des anderen Unterbaums zum Join, und genau das passiert - ein äußerer Join kann diese Anzahl nicht reduzieren, da eine nicht verknüpfte Zeile immer noch durchlaufen wird - nur mit NULL für das fvh Spalten).

Es gibt immer noch Kardinalitätsschätzungsfehler im äußeren Verknüpfungsplan, die jedoch nicht gleich groß sind, und es wird eine ausreichende Speicherzuweisung angefordert, um zu vermeiden, dass irgendwo etwas verschüttet wird.

Dieses Problem mit der Kardinalitätsschätzung wurde in der neuesten Version mit verschachtelte Ausführung behoben. In der Zwischenzeit (wie Sie in den Kommentaren sagen, muss es 2008-kompatibel sein) können Sie es manuell verschachteln, indem Sie das TVF-Ergebnis in einer #temp - Tabelle speichern und diese zusammenfügen, um die Zählung des Zwischenergebnisses (und) zu ermöglichen Spaltenstatistik) zu berücksichtigen.

6
Martin Smith