Der fehlerhafte Plan enthält eine Warnung in der temporären Tabelle, dass die Join-Spalte keine Statistiken enthält. Was gibt?
Es mag einen esoterischeren Grund dafür geben, aber es ist wahrscheinlicher, dass die Erstellung von Statistiken einfach fehlschlägt. Dies kann beispielsweise der Fall sein, wenn die Aufgabe nicht die benötigten Speicherressourcen abruft oder wenn die Erstellung von Statistiken gedrosselt wird (zu viele gleichzeitige Kompilierungen). Weitere Informationen finden Sie im Microsoft White Paper Statistics, das vom Abfrageoptimierer in Microsoft SQL Server 2008 verwendet wird . Möglicherweise können Sie dies weiter debuggen, indem Sie sich den Profiler für automatische Statistiken oder erweiterte Ereignisse und andere Ereignisse ungefähr zur gleichen Zeit ansehen.
Allerdings wären viel mehr Informationen und Untersuchungen erforderlich, um die Schuld für die Planauswahl an die Tür der fehlenden temporären Tabellenstatistik zu legen. Auch ohne detaillierte Statistiken kann der Optimierer die Gesamtkardinalität der temporären Tabelle sehen, und dies scheint hier ein wichtiger Faktor zu sein.
... kann aber gleich danach wieder auf den guten Plan umgestellt werden. Gleiche kompilierte Parameter.
Der @Months
Parameter ist möglicherweise derselbe, aber die Anzahl der Zeilen in der temporären Tabelle (aus der unbekannten Ansicht View_Feeder
) ist unterschiedlich, und die bereitgestellten Pläne zeigen nicht den Wert von an @MyId
.
Ausgehend von den verfügbaren Informationen: Der "gute" Plan (nur Schätzungen, keine Leistungsdaten angegeben) basiert auf einer temporären Tabelle mit 4 Zeilen . Der 'schlechte Plan' basiert auf einer temporären Tabelle mit 114 Zeilen . Ein Mangel an Dichte- und Histogramminformationen mag zwar nicht hilfreich sein, aber es ist leicht zu erkennen, wie der Optimierer einen anderen Plan für 4 gegenüber 114 Zeilen wählen kann, wenn auch solche mit unbekannter Dichte und Verteilung.
Wenn Schätzungen zu Planbetreibern, die nicht von der temporären Tabelle abhängig sind, stark abweichen, ist dies ein starkes Signal dafür, dass die aktuellen Haupttabellenstatistiken nicht repräsentativ für die zugrunde liegenden Daten sind. Der Mangel an Informationen in der Frage macht es unmöglich, dies zu bewerten.
Es ist jedoch erkennbar, dass der Optimierer hier aufgefordert wird, zwischen nicht optimalen Alternativen zu wählen . Keiner der vorgestellten Pläne stellt eine „offensichtlich gute“ Wahl dar, da beide Suchvorgänge (Fehlen eines „Deckungsindex“) und späte Filterung (siehe weiter unten) beinhalten. Insbesondere Lookups sind mit hohen Kosten verbunden, die empfindlich von Kardinalitätsschätzungen abhängen.
Die Verwendung einer Ansicht schränkt die Auswahlmöglichkeiten für Optimierer und Hinweise ein:
- Die Ansicht enthält eine
GROUP BY
, die verhindert, dass das Prädikat vp.Col3 > DATEADD(MONTH, @Months * -1, GETDATE())
nach unten gedrückt wird, obwohl die Transformation in diesem speziellen Fall gültig wäre.
- Das Einfügen der Ansicht in die Abfrage bietet eine natürliche Möglichkeit, die Datums- / Uhrzeitspalte früher zu filtern (obwohl in der Frage nicht angegeben ist, ob eine Umgestaltung der Abfrage eine Option ist).
- Es ist nicht möglich, einen Index in einer Ansicht anzugeben, und
FORCESEEK
der Optimierer wird lediglich aufgefordert, einen Indexsuchplan zu finden (nicht unbedingt unter Verwendung des von Ihnen bevorzugten Index). Das Entfernen der Ansicht würde diese Einschränkung ebenfalls aufheben.
Wenn Sie dem Prädikat erlauben, nach unten zu drücken, sollten sich auch Indexierungsmöglichkeiten für die große Tabelle eröffnen. Zum Beispiel:
CREATE INDEX give_me_a_good_name
ON dbo.LargeTable (Col1, [Status], Col4)
INCLUDE (P_Id);
... bietet einen guten Zugriffspfad für die umgeschriebene Abfrage:
DECLARE @Date datetime = DATEADD(MONTH, @Months * -1, GETDATE());
SELECT
MT.Col1,
ST.Col2,
MAX(LT.Col4)
FROM #MyTemp AS MT
JOIN dbo.LargeTable AS LT
ON LT.Col1 = MT.Col1
JOIN dbo.SmallTable AS ST
ON ST.P_id = LT.P_Id
WHERE
LT.[Status] IN (3, 4)
AND LT.Col4 > @Date
GROUP BY
MT.Col1,
ST.Col2
OPTION (RECOMPILE);
Eine weitere Überlegung ist die Auswirkung des Zwischenspeicherns von temporären Tabellen und Statistiken, wie in meinen Artikeln Erläuterungen zu temporären Tabellen in gespeicherten Prozeduren und zum Zwischenspeichern von temporären Tabellen beschrieben . Wenn ein guter Plan vom aktuellen Inhalt des temporären Objekts abhängt , kann eine explizite vor der Hauptabfrage und das Hinzufügen zur Hauptabfrage eine gute Lösung sein.UPDATE STATISTICS #MyTemp;
OPTION (RECOMPILE)
Wenn eine bestimmte Planform für diese Abfrage immer optimal ist, stehen Ihnen alternativ viele Optionen zur Verfügung, darunter eine Vielzahl von Hinweisen, Planleitfäden und das Erzwingen von Abfrage-Speicherplänen. Möglicherweise ist die Verwendung einer Tabellenvariablen anstelle einer temporären Tabelle die bessere Wahl, da sie den Fall niedriger Kardinalität bevorzugt und keine Statistiken bereitstellt (oder sich darauf stützt).
Zusammenfassend lässt sich sagen, dass einige allgemeine Verbesserungen vorgenommen werden sollten, bevor über die Gründe für (die Auswirkungen) gelegentlich fehlender Statistiken in der temporären Tabelle nachgedacht wird:
- Stellen Sie sicher, dass Statistiken für den Optimierer repräsentativ und nützlich sind
- Überprüfen Sie die Ist- und Schätzwerte für einen Bereich von Parameterwerten
- Stellen Sie gute Datenzugriffspfade für die Abfrage bereit, indem Sie vorhandene Indizes verbessern
- Entfernen Sie die Ansicht, wenn möglich. oder betrachten Sie eine 'parametrisierte Ansicht' (Inline-Tabellenwertfunktion) mit einem expliziten Prädikat für den Datums- / Zeitparameter.
- Stellen Sie sicher, dass die automatische Erstellung von Statistiken nicht unnötig gedrosselt wird
- Verwenden Sie die richtige Art von temporärem Objekt für die Aufgabe (Tabelle vs. Variable).
- Überlegen Sie,
RECOMPILE
ob die Planauswahl sehr empfindlich auf Parameterwerte reagiert
- Hinzufügen
UPDATE STATISTICS
und RECOMPILE
wenn zwischengespeicherte Statistiken ein Problem sind
- Stellen Sie sich eine temporäre Tabelle mit einem Primärschlüssel vor, anstatt
SELECT INTO
dem Optimierer nützliche Informationen zu liefern
- Überprüfen Sie das Schema, um sicherzustellen, dass das Optimierungsprogramm über die größtmöglichen Informationen verfügt (z. B. Fremdschlüssel, andere Einschränkungen).
- Berücksichtigen Sie die Eignung gefilterter Indizes / Statistiken basierend auf Ihrem Wissen über die Daten
- Streuen Sie keine
NOLOCK
Hinweise, um die Leistung zu steigern
Repro
Folgendes wurde aus den begrenzten Informationen erstellt, die in den bereitgestellten redigierten Ausführungsplänen verfügbar sind:
DROP VIEW IF EXISTS dbo.View_VP;
DROP TABLE IF EXISTS dbo.SmallTable, dbo.LargeTable, #MyTemp;
GO
CREATE TABLE LargeTable (P_Id integer NOT NULL, Status integer NOT NULL, Col1 integer NOT NULL, Col4 datetime NOT NULL);
CREATE TABLE SmallTable (P_id integer NOT NULL, Col2 integer NOT NULL)
CREATE TABLE #MyTemp (Col1 integer NOT NULL);
GO
CREATE VIEW dbo.View_VP
AS
SELECT
pp.Col1,
pd.Col2 AS Col2,
MAX(pp.Col4) AS Col3
FROM LargeTable pp
JOIN SmallTable pd
ON pd.P_id = pp.P_Id
WHERE
pp.[Status] IN (3, 4)
GROUP BY
pp.Col1, pd.Col2;
GO
CREATE UNIQUE CLUSTERED INDEX PK_SmallTable ON dbo.SmallTable (P_id)
CREATE CLUSTERED INDEX ix_P_id ON dbo.LargeTable (P_Id)
CREATE INDEX ix_Col1 ON dbo.LargeTable (Col1)
CREATE INDEX ix_Status ON dbo.LargeTable ([Status])
GO
UPDATE STATISTICS dbo.LargeTable WITH ROWCOUNT = 32268200, PAGECOUNT = 322682;
UPDATE STATISTICS dbo.SmallTable WITH ROWCOUNT = 6349, PAGECOUNT = 63;
UPDATE STATISTICS #MyTemp WITH ROWCOUNT = 4;
Die Abfrage lautet:
DECLARE @Months integer = 6;
SELECT wd.Col1
, vp.Col2
, vp.Col3
FROM dbo.View_VP vp WITH (FORCESEEK)
INNER JOIN #MyTemp wd ON wd.Col1 = vp.Col1
WHERE vp.Col3 > DATEADD(MONTH, @Months * -1, GETDATE())
Ohne echte Statistiken zu den Basistabellen werden Pläne bevorzugt, die dem Beispiel "schlechter Plan" nahe kommen (unter Verwendung ix_Status
):
Dies legt nahe, dass Informationen über die Selektivität von Col1
ein wichtiger Faktor bei der Auswahl des Optimierers sind.