Warum die Suche nicht vom Optimierer ausgewählt wird
TL: DR Die erweiterte berechnete Spaltendefinition beeinträchtigt die Fähigkeit des Optimierers, Verknüpfungen zunächst neu anzuordnen. Mit einem anderen Ausgangspunkt nimmt die kostenbasierte Optimierung einen anderen Weg durch das Optimierungsprogramm und führt zu einer anderen endgültigen Planauswahl.
Einzelheiten
Bei allen außer den einfachsten Abfragen versucht der Optimierer nicht, den gesamten Bereich möglicher Pläne zu untersuchen. Stattdessen wird ein vernünftig aussehender Ausgangspunkt ausgewählt und anschließend in einer oder mehreren Suchphasen mit einem begrenzten Aufwand logische und physische Variationen untersucht, bis ein vernünftiger Plan gefunden ist.
Der Hauptgrund, warum Sie für beide Fälle unterschiedliche Pläne (mit unterschiedlichen endgültigen Kostenschätzungen) erhalten, ist, dass es unterschiedliche Ausgangspunkte gibt. Ausgehend von einem anderen Ort endet die Optimierung an einem anderen Ort (nach einer begrenzten Anzahl von Explorations- und Implementierungsiterationen). Ich hoffe das ist einigermaßen intuitiv.
Der von mir erwähnte Ausgangspunkt basiert in gewisser Weise auf der Textdarstellung der Abfrage, es werden jedoch Änderungen an der internen Baumdarstellung vorgenommen, während diese die Phasen der Analyse, Bindung, Normalisierung und Vereinfachung der Abfragekompilierung durchläuft.
Wichtig ist, dass der genaue Startpunkt stark von der vom Optimierer ausgewählten anfänglichen Verknüpfungsreihenfolge abhängt . Diese Auswahl wird getroffen, bevor Statistiken geladen werden und bevor Kardinalitätsschätzungen abgeleitet wurden. Die Gesamtkardinalität (Anzahl der Zeilen) in jeder Tabelle ist jedoch bekannt, da sie aus Systemmetadaten erhalten wurde.
Die anfängliche Join-Reihenfolge basiert daher auf Heuristiken . Das Optimierungsprogramm versucht beispielsweise, den Baum so umzuschreiben, dass kleinere Tabellen vor größeren verknüpft werden und innere Verknüpfungen vor äußeren Verknüpfungen (und Kreuzverknüpfungen) erfolgen.
Das Vorhandensein der berechneten Spalte stört diesen Prozess, insbesondere die Fähigkeit des Optimierers, äußere Verknüpfungen in den Abfragebaum zu verschieben. Dies liegt daran, dass die berechnete Spalte vor der Neuordnung der Verknüpfung in ihren zugrunde liegenden Ausdruck erweitert wird und das Verschieben einer Verknüpfung an einem komplexen Ausdruck vorbei viel schwieriger ist als das Verschieben an einer einfachen Spaltenreferenz.
Die beteiligten Bäume sind ziemlich groß, aber zur Veranschaulichung beginnt der nicht berechnete anfängliche Abfragebaum der Spalte mit: (Beachten Sie die beiden äußeren Verknüpfungen oben)
LogOp_Select
LogOp_Apply (x_jtLeftOuter)
LogOp_LeftOuterJoin
LogOp_NAryJoin
LogOp_LeftAntiSemiJoin
LogOp_NAryJoin
LogOp_Get TBL: dbo.table1 (Alias TBL: a4)
LogOp_Select
LogOp_Get TBL: dbo.table6 (Alias TBL: a3)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a3] .col18
ScaOp_Const TI (Varchar Collate 53256, Var, Trim, ML = 16)
LogOp_Select
LogOp_Get TBL: dbo.table1 (Alias TBL: a1)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a1] .col2
ScaOp_Const TI (Varchar Collate 53256, Var, Trim, ML = 16)
LogOp_Select
LogOp_Get TBL: dbo.table5 (Alias TBL: a2)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a2] .col2
ScaOp_Const TI (Varchar Collate 53256, Var, Trim, ML = 16)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a4] .col2
ScaOp_Identifier QCOL: [a3] .col19
LogOp_Select
LogOp_Get TBL: dbo.table7 (Alias TBL: a7)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a7] .col22
ScaOp_Const TI (Varchar Collate 53256, Var, Trim, ML = 16)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a4] .col2
ScaOp_Identifier QCOL: [a7] .col23
LogOp_Select
LogOp_Get TBL: table1 (Alias TBL: cdc)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [cdc] .col6
ScaOp_Const TI (smallint, ML = 2) XVAR (smallint, nicht im Besitz, Wert = 4)
LogOp_Get TBL: dbo.table5 (Alias TBL: a5)
LogOp_Get TBL: table2 (Alias TBL: cdt)
ScaOp_Logical x_lopAnd
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a5] .col2
ScaOp_Identifier QCOL: [cdc] .col2
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a4] .col2
ScaOp_Identifier QCOL: [cdc] .col2
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [cdt] .col1
ScaOp_Identifier QCOL: [cdc] .col1
LogOp_Get TBL: table3 (Alias TBL: ahcr)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [ahcr] .col9
ScaOp_Identifier QCOL: [cdt] .col1
Das gleiche Fragment der berechneten Spaltenabfrage lautet: (Beachten Sie die äußere Verknüpfung viel weiter unten, die erweiterte Definition der berechneten Spalte und einige andere subtile Unterschiede in der (inneren) Verknüpfungsreihenfolge.)
LogOp_Select
LogOp_Apply (x_jtLeftOuter)
LogOp_NAryJoin
LogOp_LeftAntiSemiJoin
LogOp_NAryJoin
LogOp_Get TBL: dbo.table1 (Alias TBL: a4)
LogOp_Select
LogOp_Get TBL: dbo.table6 (Alias TBL: a3)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a3] .col18
ScaOp_Const TI (Varchar Collate 53256, Var, Trim, ML = 16)
LogOp_Select
LogOp_Get TBL: dbo.table1 (Alias TBL: a1
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a1] .col2
ScaOp_Const TI (Varchar Collate 53256, Var, Trim, ML = 16)
LogOp_Select
LogOp_Get TBL: dbo.table5 (Alias TBL: a2)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a2] .col2
ScaOp_Const TI (Varchar Collate 53256, Var, Trim, ML = 16)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a4] .col2
ScaOp_Identifier QCOL: [a3] .col19
LogOp_Select
LogOp_Get TBL: dbo.table7 (Alias TBL: a7)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a7] .col22
ScaOp_Const TI (Varchar Collate 53256, Var, Trim, ML = 16)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a4] .col2
ScaOp_Identifier QCOL: [a7] .col23
LogOp_Project
LogOp_LeftOuterJoin
LogOp_Join
LogOp_Select
LogOp_Get TBL: table1 (Alias TBL: cdc)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [cdc] .col6
ScaOp_Const TI (smallint, ML = 2) XVAR (smallint, nicht im Besitz, Wert = 4)
LogOp_Get TBL: table2 (Alias TBL: cdt)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [cdc] .col1
ScaOp_Identifier QCOL: [cdt] .col1
LogOp_Get TBL: table3 (Alias TBL: ahcr)
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [ahcr] .col9
ScaOp_Identifier QCOL: [cdt] .col1
AncOp_PrjList
AncOp_PrjEl QCOL: [cdc] .col7
ScaOp_Convert char collate 53256, Null, Trim, ML = 6
ScaOp_IIF varchar collate 53256, Null, Var, Trim, ML = 6
ScaOp_Comp x_cmpEq
ScaOp_Intrinsic ist numerisch
ScaOp_Intrinsic richtig
ScaOp_Identifier QCOL: [cdc] .col4
ScaOp_Const TI (int, ML = 4) XVAR (int, nicht im Besitz, Wert = 4)
ScaOp_Const TI (int, ML = 4) XVAR (int, nicht im Besitz, Wert = 0)
ScaOp_Const TI (varchar collate 53256, Var, Trim, ML = 1) XVAR (varchar, Owned, Value = Len, Data = (0,))
ScaOp_Intrinsic-Teilzeichenfolge
ScaOp_Const TI (int, ML = 4) XVAR (int, nicht im Besitz, Wert = 6)
ScaOp_Const TI (int, ML = 4) XVAR (int, nicht im Besitz, Wert = 1)
ScaOp_Identifier QCOL: [cdc] .col4
LogOp_Get TBL: dbo.table5 (Alias TBL: a5)
ScaOp_Logical x_lopAnd
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a5] .col2
ScaOp_Identifier QCOL: [cdc] .col2
ScaOp_Comp x_cmpEq
ScaOp_Identifier QCOL: [a4] .col2
ScaOp_Identifier QCOL: [cdc] .col2
Statistiken werden geladen und eine anfängliche Kardinalitätsschätzung wird für den Baum durchgeführt, unmittelbar nachdem die anfängliche Verknüpfungsreihenfolge festgelegt wurde. Die Verknüpfungen in unterschiedlichen Reihenfolgen wirken sich auch auf diese Schätzungen aus und wirken sich daher auf die spätere kostenbasierte Optimierung aus.
Wenn in diesem Abschnitt ein äußerer Join in der Mitte des Baums steckt, kann dies verhindern, dass während der kostenbasierten Optimierung weitere Regeln für die Neuordnung von Joins neu angeordnet werden.
Durch die Verwendung eines Planleitfadens (oder gleichwertig eines USE PLAN
Hinweises - Beispiel für Ihre Abfrage ) wird die Suchstrategie in einen zielorientierteren Ansatz geändert , der sich an der allgemeinen Form und den Merkmalen der bereitgestellten Vorlage orientiert. Dies erklärt , warum der Optimierer kann das gleiche finden table1
suchen Plan gegen beide berechnet und nicht berechnete Spalte Schemata, wenn ein Plan Anleitung oder Hinweis verwendet wird.
Ob wir etwas anders machen können, um die Suche zu ermöglichen
Dies ist etwas, worüber Sie sich nur Sorgen machen müssen, wenn der Optimierer selbst keinen Plan mit akzeptablen Leistungsmerkmalen findet.
Alle normalen Tuning-Tools sind möglicherweise anwendbar. Sie können beispielsweise die Abfrage in einfachere Teile aufteilen, die verfügbare Indizierung überprüfen und verbessern, Statistiken aktualisieren oder neue Statistiken erstellen ... und so weiter.
All diese Dinge können sich auf Kardinalitätsschätzungen und den Codepfad durch den Optimierer auswirken und kostenbasierte Entscheidungen auf subtile Weise beeinflussen.
Sie können letztendlich auf Hinweise (oder einen Planleitfaden) zurückgreifen, aber das ist normalerweise nicht die ideale Lösung.
Zusätzliche Fragen aus Kommentaren
Ich bin damit einverstanden, dass es am besten ist, die Abfrage usw. zu vereinfachen. Gibt es jedoch eine Möglichkeit (Trace-Flag), das Optimierungsprogramm mit der Optimierung fortzusetzen und das gleiche Ergebnis zu erzielen?
Nein, es gibt kein Trace-Flag, um eine umfassende Suche durchzuführen, und Sie möchten keines. Der mögliche Suchraum ist riesig und Kompilierungszeiten, die das Alter des Universums überschreiten, würden nicht gut angenommen. Außerdem kennt der Optimierer nicht jede mögliche logische Transformation (niemand kennt sie).
Warum ist die komplexe Erweiterung erforderlich, da die Spalte bestehen bleibt? Warum kann das Optimierungsprogramm es nicht vermeiden, es zu erweitern, es wie eine normale Spalte zu behandeln und denselben Ausgangspunkt zu erreichen?
Berechnete Spalten werden erweitert (wie Ansichten), um zusätzliche Optimierungsmöglichkeiten zu ermöglichen. Die Erweiterung kann später im Prozess möglicherweise auf eine persistente Spalte oder einen Index zurückgeführt werden. Dies geschieht jedoch, nachdem die anfängliche Verknüpfungsreihenfolge festgelegt wurde.