Warum unterscheiden sich die geschätzten Kosten von (denselben) 1000 Suchanfragen für einen eindeutigen Index in diesen Plänen?

27

In den folgenden Abfragen wird geschätzt, dass beide Ausführungspläne 1.000 Suchvorgänge für einen eindeutigen Index ausführen.

Die Suchvorgänge werden von einem geordneten Scan in derselben Quelltabelle gesteuert, sodass anscheinend dieselben Werte in derselben Reihenfolge gesucht werden sollten.

Beide verschachtelten Schleifen haben <NestedLoops Optimized="false" WithOrderedPrefetch="true">

Weiß jemand, warum diese Aufgabe bei 0.172434 im ersten Plan aber 3.01702 im zweiten kostet?

(Der Grund für die Frage ist, dass mir die erste Abfrage aufgrund der offensichtlich viel geringeren Planungskosten als Optimierung vorgeschlagen wurde. Es sieht für mich tatsächlich so aus, als würde es mehr funktionieren, aber ich versuche nur, die Diskrepanz zu erklären.) .)

Installieren

CREATE TABLE dbo.Target(KeyCol int PRIMARY KEY, OtherCol char(32) NOT NULL);

CREATE TABLE dbo.Staging(KeyCol int PRIMARY KEY, OtherCol char(32) NOT NULL); 

INSERT INTO dbo.Target
SELECT TOP (1000000) ROW_NUMBER() OVER (ORDER BY @@SPID), LEFT(NEWID(),32)
FROM master..spt_values v1,  
     master..spt_values v2;

INSERT INTO dbo.Staging
SELECT TOP (1000) ROW_NUMBER() OVER (ORDER BY @@SPID), LEFT(NEWID(),32)
FROM master..spt_values v1;

Abfrage 1 Link "Plan einfügen"

WITH T
     AS (SELECT *
         FROM   Target AS T
         WHERE  T.KeyCol IN (SELECT S.KeyCol
                             FROM   Staging AS S))
MERGE T
USING Staging S
ON ( T.KeyCol = S.KeyCol )
WHEN NOT MATCHED THEN
  INSERT ( KeyCol, OtherCol )
  VALUES(S.KeyCol, S.OtherCol )
WHEN MATCHED AND T.OtherCol > S.OtherCol THEN
  UPDATE SET T.OtherCol = S.OtherCol;

Abfrage 2 Link "Plan einfügen"

MERGE Target T
USING Staging S
ON ( T.KeyCol = S.KeyCol )
WHEN NOT MATCHED THEN
  INSERT ( KeyCol, OtherCol )
  VALUES( S.KeyCol, S.OtherCol )
WHEN MATCHED AND T.OtherCol > S.OtherCol THEN
  UPDATE SET T.OtherCol = S.OtherCol; 

Abfrage 1

Abfrage 2

Das Obige wurde unter SQL Server 2014 (SP2) (KB3171021) - 12.0.5000.0 (X64) getestet.


@ Joe Obbish weist in den Kommentaren darauf hin, dass ein einfacherer Repro wäre

SELECT *
FROM staging AS S 
  LEFT OUTER JOIN Target AS T 
    ON T.KeyCol = S.KeyCol;

vs

SELECT *
FROM staging AS S 
  LEFT OUTER JOIN (SELECT * FROM Target) AS T 
    ON T.KeyCol = S.KeyCol;

Für die Staging-Tabelle mit 1.000 Zeilen haben beide oben genannten weiterhin dieselbe Planform mit verschachtelten Schleifen und den Plan, ohne dass die abgeleitete Tabelle billiger erscheint. Bei einer Staging-Tabelle mit 10.000 Zeilen und derselben Zieltabelle wie oben ändert sich der Plan jedoch durch die Kostendifferenz Form (mit einem vollständigen Scan- und Merge-Join, der relativ attraktiver zu sein scheint als teure Suchvorgänge), die diese Kostendifferenz aufzeigt, kann andere Auswirkungen haben, als nur den Vergleich von Plänen zu erschweren.

Bildbeschreibung hier eingeben

Martin Smith
quelle

Antworten:

20

Weiß jemand, warum diese Aufgabe bei 0.172434 im ersten Plan aber 3.01702 im zweiten kostet?

Im Allgemeinen wird eine Suche nach einer Innenseite unter einem Join mit verschachtelten Schleifen unter der Annahme eines zufälligen E / A-Musters berechnet. Es gibt eine einfache ersatzbasierte Reduzierung für nachfolgende Zugriffe, die die Möglichkeit berücksichtigt, dass die erforderliche Seite bereits durch eine vorherige Iteration in den Speicher gebracht wurde. Diese Basisbewertung ergibt die Standardkosten (höhere Kosten).

Es gibt noch einen weiteren Kalkulationseingang, Smart Seek Costing , über den nur wenige Details bekannt sind. Ich vermute (und das ist alles, was es in diesem Stadium ist), dass SSC versucht, die I / O-Kosten der inneren Seite genauer zu bewerten, möglicherweise durch Berücksichtigung der lokalen Reihenfolge und / oder des Wertebereichs, der abgerufen werden soll. Wer weiß.

Beispielsweise wird bei der ersten Suchoperation nicht nur die angeforderte Zeile, sondern alle Zeilen auf dieser Seite (in Indexreihenfolge) eingefügt. In Anbetracht des allgemeinen Zugriffsmusters sind zum Abrufen der 1000 Zeilen in 1000 Suchläufen nur 2 physische Lesevorgänge erforderlich, auch wenn das Vorauslesen und das Vorauslesen deaktiviert sind. Aus dieser Perspektive stellt die Standard-E / A-Kostenberechnung eine erhebliche Überschätzung dar, und die SSC-bereinigten Kosten entsprechen eher der Realität.

Es ist zu erwarten, dass SSC am effektivsten ist, wenn die Schleife eine Indexsuche mehr oder weniger direkt ausführt, und die äußere Verknüpfungsreferenz die Basis für die Suchoperation ist. Soweit ich weiß, wird SSC immer für geeignete physische Operationen versucht, führt jedoch häufig zu keiner Abwärtskorrektur, wenn die Suche durch andere Operationen vom Join getrennt wird. Einfache Filter bilden eine Ausnahme, möglicherweise weil SQL Server diese häufig in den Datenzugriffsoperator pushen kann. In jedem Fall unterstützt der Optimierer die Auswahl ziemlich genau.

Es ist bedauerlich, dass der Berechnungsmaßstab für die äußeren Projektionen der Unterabfrage hier SSC zu stören scheint. Berechnungsskalare werden normalerweise über dem Join verschoben, aber diese müssen dort bleiben, wo sie sind. Trotzdem sind die meisten normalen Compute-Skalare für die Optimierung ziemlich transparent, was ein wenig überraschend ist.

Unabhängig davon PhyOp_Rangeist SelIdxToRngSSC effektiv , wenn die physische Operation aus einer einfachen Auswahl in einem Index erstellt wird. Wenn die komplexere SelToIdxStrategy(Auswahl einer Tabelle zu einer Indexstrategie) angewendet wird, ergibt sich diePhyOp_Range SSC aus, führt jedoch zu keiner Reduzierung. Wieder scheint es, dass einfachere, direktere Operationen am besten mit SSC funktionieren.

Ich wünschte, ich könnte Ihnen genau sagen, was SSC macht, und die genauen Berechnungen zeigen, aber ich kenne diese Details nicht. Wenn Sie die begrenzte Trace-Ausgabe untersuchen möchten, die für Sie verfügbar ist, können Sie das Flag 2398 für nicht dokumentierte Trace-Nachrichten verwenden. Eine Beispielausgabe ist:

Intelligente Suchkostenberechnung (7.1) :: 1.34078e + 154, 0.001

Dieses Beispiel bezieht sich auf die Memogruppe 7, Alternative 1, mit einer Kostenobergrenze und einem Faktor von 0,001. Stellen Sie sicher, dass die Tabellen ohne Parallelität neu erstellt werden, damit die Seiten so dicht wie möglich sind. Andernfalls ist der Faktor für Ihre Beispiel-Zieltabelle eher 0,000821. Natürlich gibt es da einige ziemlich offensichtliche Zusammenhänge.

SSC kann auch mit undokumentiertem Ablaufverfolgungsflag 2399 deaktiviert werden. Wenn dieses Flag aktiv ist, sind beide Kosten der höhere Wert.

Paul White sagt GoFundMonica
quelle
8

Ich bin nicht sicher, ob dies eine Antwort ist, aber es ist ein bisschen lang für einen Kommentar. Die Ursache für den Unterschied ist reine Spekulation meinerseits und kann vielleicht ein Denkanstoß für andere sein.

Vereinfachte Abfragen mit Ausführungsplänen.

SELECT S.KeyCol, 
       S.OtherCol,
       T.*
FROM staging AS S 
  LEFT OUTER JOIN Target AS T 
    ON T.KeyCol = S.KeyCol;

SELECT S.KeyCol, 
       S.OtherCol,
       T.*
FROM staging AS S 
  LEFT OUTER JOIN (
                  SELECT *
                  FROM Target
                  ) AS T 
    ON T.KeyCol = S.KeyCol;

Bildbeschreibung hier eingeben

Der Hauptunterschied zwischen diesen äquivalenten Abfragen, die tatsächlich zu identischen Ausführungsplänen führen könnten, ist der Rechenskalaroperator. Ich weiß nicht, warum es dort sein muss, aber ich denke, das ist so weit wie der Optimierer gehen kann, um die abgeleitete Tabelle zu optimieren.

Ich vermute, dass das Vorhandensein des Computing-Skalars die IO-Kosten für die zweite Abfrage in die Höhe treibt.

Aus dem Optimierer heraus: Kosten planen

Die CPU-Kosten werden für die erste Zeile mit 0,0001581 und für die nachfolgenden Zeilen mit 0,000011 berechnet.
...
Die I / O - Kosten von 0.003125 genau 1/320 - reflektieren die Annahme des Modells , dass das Disk - Subsystem 320 Random - I / O - Operationen pro Sekunde ausführen kann
...
die Nachkalkulation Komponente ist intelligent genug , dass die Gesamtzahl der erkennen Seiten, die von der Festplatte eingelesen werden müssen, dürfen niemals die Anzahl der Seiten überschreiten, die zum Speichern der gesamten Tabelle erforderlich sind.

In meinem Fall umfasst die Tabelle 5618 Seiten, und um 1000 Zeilen aus 1000000 Zeilen zu erhalten, wird eine geschätzte Anzahl von 5,618 Seiten benötigt, was die IO-Kosten von 0,015625 ergibt.

Die CPU-Kosten für beide Abfragen müssen gleich sein. 0.0001581 * 1000 executions = 0.1581 .

Entsprechend dem oben verlinkten Artikel können wir die Kosten für die erste Abfrage mit 0,173725 berechnen.

Und vorausgesetzt, ich habe Recht damit, wie der Computing-Skalar die IO-Kosten durcheinanderbringt, kann er auf 3,2831 berechnet werden.

Nicht genau das, was in den Plänen gezeigt wird, aber es ist genau dort in der Nachbarschaft.

Mikael Eriksson
quelle
6

(Dies wäre besser als Kommentar zu Pauls Antwort, aber ich habe noch nicht genug Repräsentanten.)

Ich wollte die Liste der Trace-Flags (und ein paar DBCCAussagen) bereitstellen, die ich früher zu einem fast vollständigen Ergebnis gebracht habe, falls es hilfreich sein sollte, ähnliche Diskrepanzen in Zukunft zu untersuchen. All dies sollte nicht für die Produktion verwendet werden .

Zuerst habe ich mir das Final Memo angesehen, um zu sehen, welche physischen Operatoren verwendet wurden. Sie sehen nach den grafischen Ausführungsplänen mit Sicherheit gleich aus. Also habe ich Trace-Flags verwendet 3604und 8615die erste leitet die Ausgabe an den Client weiter und die zweite enthüllt das endgültige Memo:

SELECT S.*, T.KeyCol
FROM Staging AS S
      LEFT OUTER JOIN Target AS T
       ON T.KeyCol = S.KeyCol
OPTION(QUERYTRACEON 3604, -- Output client info
       QUERYTRACEON 8615, -- Shows Final Memo structure
       RECOMPILE);

Ausgehend von der habe Root Groupich diese nahezu identischen PhyOp_RangeOperatoren gefunden:

  1. PhyOp_Range 1 ASC 2.0 Cost(RowGoal 0,ReW 0,ReB 999,Dist 1000,Total 1000)= 0.175559(Distance = 2)
  2. PhyOp_Range 1 ASC 3.0 Cost(RowGoal 0,ReW 0,ReB 999,Dist 1000,Total 1000)= 3.01702(Distance = 2)

Der einzige offensichtliche Unterschied für mich war das 2.0und3.0 , das sich auf die jeweilige "Memogruppe 2, Original" und "Memogruppe 3, Original" bezieht. Wenn Sie das Memo überprüfen, beziehen sich diese auf dasselbe - es wurden also noch keine Unterschiede festgestellt.

Zweitens habe ich eine ganze Reihe von Spurenfahnen untersucht, die sich für mich als fruchtlos erwiesen haben - aber einen interessanten Inhalt haben. Ich habe mich am meisten von Benjamin Nevarez gelöst . Ich suchte nach Hinweisen auf Optimierungsregeln, die in dem einen Fall und nicht in dem anderen angewendet wurden.

 SELECT S.*, T.KeyCol
 FROM Staging AS S
      LEFT OUTER JOIN Target AS T
        ON T.KeyCol = S.KeyCol
 OPTION (QUERYTRACEON 3604, -- Output info to client
         QUERYTRACEON 2363, -- Show stats and cardinality info
         QUERYTRACEON 8675, -- Show optimization process info
         QUERYTRACEON 8606, -- Show logical query trees
         QUERYTRACEON 8607, -- Show physical query tree
         QUERYTRACEON 2372, -- Show memory utilization info for optimization stages 
         QUERYTRACEON 2373, -- Show memory utilization info for applying rules
         RECOMPILE );

Drittens habe ich mir angesehen, welche Regeln für unsere PhyOp_Ranges angewendet wurden , die so ähnlich aussehen. Ich habe ein paar Trace-Flags benutzt, die Paul in einem Blog-Post erwähnt hat .

SELECT S.*, T.KeyCol
FROM Staging AS S
      LEFT OUTER JOIN (SELECT KeyCol
                      FROM Target) AS T
       ON T.KeyCol = S.KeyCol
OPTION (QUERYTRACEON 3604, -- Output info to client
        QUERYTRACEON 8619, -- Show applied optimization rules
        QUERYTRACEON 8620, -- Show rule-to-memo info
        QUERYTRACEON 8621, -- Show resulting tree
        QUERYTRACEON 2398, -- Show "smart seek costing"
        RECOMPILE );

Vom Ausgang sehen wir , dass die Direct- JOINdiese Regel unsere erhalten angewendet PhyOp_RangeBetreiber: Rule Result: group=7 2 <SelIdxToRng>PhyOp_Range 1 ASC 2 (Distance = 2). Die subselect angewendet diese Regel statt: Rule Result: group=9 2 <SelToIdxStrategy>PhyOp_Range 1 ASC 3 (Distance = 2). Hier sehen Sie auch die Informationen zur "intelligenten Suchkostenberechnung", die mit jeder Regel verknüpft sind. Für die Direct- JOINist dies der Ausgang (für mich): Smart seek costing (7.2) :: 1.34078e+154 , 0.001. Für die Unterauswahl ist dies die Ausgabe:Smart seek costing (9.2) :: 1.34078e+154 , 1 .

Am Ende konnte ich nicht viel schließen - aber Pauls Antwort schließt den größten Teil der Lücke. Ich würde gerne weitere Informationen zur Kostenermittlung für intelligente Suchanfragen erhalten.

Steven Hibble
quelle
4

Dies ist auch keine wirkliche Antwort - wie Mikael bemerkte, ist es schwierig, dieses Problem in den Kommentaren zu diskutieren ...

Interessanterweise sehen Sie, wenn Sie die Unterabfrage (select KeyCol FROM Target)in eine Inline-TVF konvertieren , dass der Plan und seine Kosten mit der einfachen ursprünglichen Abfrage identisch sind:

CREATE FUNCTION dbo.cs_test()
RETURNS TABLE
WITH SCHEMABINDING
AS 
RETURN (
    SELECT KeyCol FROM dbo.Target
    );

/* "normal" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN Target AS T ON T.KeyCol = S.KeyCol;

/* "subquery" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN (SELECT KeyCol FROM Target) AS T ON T.KeyCol = S.KeyCol;

/* "inline-TVF" variant */
SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN dbo.cs_test() t ON s.KeyCol = t.Keycol

Die Abfragepläne ( pastetheplan link ):

Bildbeschreibung hier eingeben

Der Abzug lässt mich glauben, dass die Kalkulationsmaschine verwirrt ist über die möglichen Auswirkungen, die diese Art der Unterabfrage haben kann .

Nehmen wir zum Beispiel Folgendes:

SELECT S.KeyCol, s.OtherCol, T.KeyCol 
FROM staging AS S 
    LEFT OUTER JOIN (
        SELECT KeyCol = CHECKSUM(NEWID()) 
        FROM Target
        ) AS T ON T.KeyCol = S.KeyCol;

Wie würden Sie das kosten? Das Abfrageoptimierungsprogramm wählt einen Plan, der der obigen "Unterabfrage" -Variante sehr ähnlich ist und einen Berechnungsskalar enthält ( Link pastetheplan.com ):

Bildbeschreibung hier eingeben

Der Berechnungsskalar hat ganz andere Kosten als die oben gezeigte "Unterabfrage" -Variante, es ist jedoch immer noch eine Vermutung, da der Abfrageoptimierer nicht a priori wissen kann, wie viele Zeilen möglicherweise zurückgegeben werden. Der Plan verwendet eine Hash-Übereinstimmung für die linke äußere Verknüpfung, da die Zeilenschätzungen nicht bekannt sind und daher auf die Anzahl der Zeilen in der Zieltabelle festgelegt sind.

Bildbeschreibung hier eingeben

Ich kann daraus keine großartige Schlussfolgerung ziehen, außer dass ich der Arbeit von Mikael in seiner Antwort zustimme, und ich hoffe, dass jemand anderes eine bessere Antwort finden kann.

Max Vernon
quelle