Der Plan wurde auf einer SQL Server 2008 R2-RTM-Instanz (Build 10.50.1600) kompiliert. Sie sollten Service Pack 3 (Build 10.50.6000) installieren , gefolgt von den neuesten Patches, um es auf den (aktuellen) neuesten Build 10.50.6542 zu bringen. Dies ist aus einer Reihe von Gründen wichtig, darunter Sicherheit, Fehlerkorrekturen und neue Funktionen.
Die Parameter-Einbettungsoptimierung
In Bezug auf die vorliegende Frage hat SQL Server 2008 R2 RTM die Parameter Embedding Optimization (PEO) für nicht unterstützt OPTION (RECOMPILE)
. Im Moment zahlen Sie die Kosten für die Neukompilierung, ohne einen der Hauptvorteile zu erkennen.
Wenn PEO verfügbar ist, kann SQL Server die in lokalen Variablen und Parametern gespeicherten Literalwerte direkt im Abfrageplan verwenden. Dies kann zu dramatischen Vereinfachungen und Leistungssteigerungen führen. Weitere Informationen dazu finden Sie in meinem Artikel Parameter Sniffing, Embedding und den RECOMPILE-Optionen .
Hash, Sortieren und Austauschen von verschüttetem Material
Diese werden nur in Ausführungsplänen angezeigt, wenn die Abfrage unter SQL Server 2012 oder höher kompiliert wurde. In früheren Versionen mussten wir Überläufe überwachen, während die Abfrage mit dem Profiler oder Extended Events ausgeführt wurde. Verschüttete Daten führen immer zu physischen E / A- Vorgängen in (und aus) dem permanenten Speicher-Backing- Tempdb , was schwerwiegende Auswirkungen auf die Leistung haben kann, insbesondere wenn die Verschüttung groß ist oder der E / A-Pfad unter Druck steht.
In Ihrem Ausführungsplan gibt es zwei Hash-Match-Operatoren (aggregiert). Der für die Hash-Tabelle reservierte Speicher basiert auf der Schätzung für Ausgabezeilen (dh er ist proportional zur Anzahl der zur Laufzeit gefundenen Gruppen). Der zugewiesene Speicher wird unmittelbar vor Beginn der Ausführung festgelegt und kann während der Ausführung nicht vergrößert werden, unabhängig davon, wie viel freier Speicher in der Instanz vorhanden ist. Im bereitgestellten Plan erzeugen beide Hash-Match-Operatoren (Aggregatoperatoren) mehr Zeilen als vom Optimierer erwartet. Daher kann es zur Laufzeit zu einem Überlauf auf tempdb kommen .
Der Plan enthält auch einen Hash-Match-Operator (Inner Join). Der für die Hash-Tabelle reservierte Speicher basiert auf der Schätzung für probenseitige Eingabezeilen . Die Probe-Eingabe schätzt 847.399 Zeilen, aber 1.223.636 werden zur Laufzeit angetroffen. Dieser Überschuss kann auch ein Überlaufen von Hasch verursachen.
Redundantes Aggregat
Die Hash-Übereinstimmung (Aggregat) an Knoten 8 führt eine Gruppierungsoperation für aus (Assortment_Id, CustomAttrID)
, aber die Eingabezeilen sind gleich den Ausgabezeilen:
Dies deutet darauf hin, dass die Spaltenkombination ein Schlüssel ist (daher ist die Gruppierung semantisch nicht erforderlich). Die Kosten für die Ausführung des redundanten Aggregats werden durch die Notwendigkeit erhöht, die 1,4 Millionen Zeilen zweimal über Hash-Partitionierungs-Börsen (die Parallelismus-Operatoren auf beiden Seiten) zu übertragen.
Da die beteiligten Spalten aus unterschiedlichen Tabellen stammen, ist es schwieriger als üblich, diese Eindeutigkeitsinformationen an das Optimierungsprogramm zu übermitteln, sodass redundante Gruppierungsvorgänge und unnötiger Austausch vermieden werden.
Ineffiziente Threadverteilung
Wie in der Antwort von Joe Obbish angemerkt , verwendet der Austausch am Knoten 14 eine Hash-Partitionierung, um Zeilen unter Threads zu verteilen. Aufgrund der geringen Anzahl von Zeilen und verfügbaren Schedulern landen leider alle drei Zeilen in einem einzigen Thread. Der scheinbar parallele Plan läuft seriell (mit parallelem Overhead) bis zur Vermittlung am Knoten 9.
Sie können dieses Problem beheben (um eine Round-Robin- oder Broadcast-Partitionierung zu erhalten), indem Sie die Option "Distinct Sort" auf Knoten 13 entfernen. Am einfachsten erstellen Sie dazu einen gruppierten Primärschlüssel für die #temp
Tabelle und führen die eindeutige Operation aus, wenn Sie die Tabelle laden:
CREATE TABLE #Temp
(
id integer NOT NULL PRIMARY KEY CLUSTERED
);
INSERT #Temp
(
id
)
SELECT DISTINCT
CAV.id
FROM @customAttrValIds AS CAV
WHERE
CAV.id IS NOT NULL;
Temporäres Caching von Tabellenstatistiken
Trotz der Verwendung von OPTION (RECOMPILE)
kann SQL Server das temporäre Tabellenobjekt und die zugehörigen Statistiken zwischen den Prozeduraufrufen zwischenspeichern. Dies ist im Allgemeinen eine willkommene Leistungsoptimierung. Wenn die temporäre Tabelle jedoch bei benachbarten Prozeduraufrufen mit einer ähnlichen Datenmenge gefüllt ist, basiert der neu kompilierte Plan möglicherweise auf falschen Statistiken (die aus einer früheren Ausführung zwischengespeichert wurden). Dies wird in meinen Artikeln, Temporäre Tabellen in gespeicherten Prozeduren und Zwischenspeichern von temporären Tabellen, erläutert .
Um dies zu vermeiden, verwenden Sie diese Option OPTION (RECOMPILE)
zusammen mit einer expliziten, UPDATE STATISTICS #TempTable
nachdem die temporäre Tabelle ausgefüllt wurde und bevor in einer Abfrage auf sie verwiesen wird.
Abfrage umschreiben
In diesem Teil wird davon ausgegangen, dass die Änderungen an der Erstellung der #Temp
Tabelle bereits vorgenommen wurden.
Angesichts der Kosten möglicher Hash-Verschüttungen und des redundanten Aggregats (und der umgebenden Börsen) kann es sich lohnen, die Menge an Knoten 10 zu materialisieren:
CREATE TABLE #Temp2
(
CustomAttrID integer NOT NULL,
Assortment_Id integer NOT NULL,
);
INSERT #Temp2
(
Assortment_Id,
CustomAttrID
)
SELECT
ACAV.Assortment_Id,
CAV.CustomAttrID
FROM #temp AS T
JOIN dbo.CustomAttributeValues AS CAV
ON CAV.Id = T.id
JOIN dbo.AssortmentCustomAttributeValues AS ACAV
ON T.id = ACAV.CustomAttributeValue_Id;
ALTER TABLE #Temp2
ADD CONSTRAINT PK_#Temp2_Assortment_Id_CustomAttrID
PRIMARY KEY CLUSTERED (Assortment_Id, CustomAttrID);
Das PRIMARY KEY
wird in einem separaten Schritt hinzugefügt, um sicherzustellen, dass die Indexerstellung genaue Informationen zur Kardinalität enthält, und um das Problem der Zwischenspeicherung temporärer Tabellenstatistiken zu vermeiden.
Es ist sehr wahrscheinlich, dass diese Materialisierung im Arbeitsspeicher auftritt (Vermeidung von Tempdb- E / A), wenn die Instanz über genügend Arbeitsspeicher verfügt. Dies ist noch wahrscheinlicher, wenn Sie ein Upgrade auf SQL Server 2012 (SP1 CU10 / SP2 CU1 oder höher) durchführen, wodurch das Eager Write-Verhalten verbessert wurde .
Durch diese Aktion erhält der Optimierer genaue Informationen zur Kardinalität des Zwischensatzes, kann Statistiken erstellen und (Assortment_Id, CustomAttrID)
als Schlüssel deklarieren .
Der Plan für die Grundgesamtheit von #Temp2
sollte folgendermaßen aussehen (beachten Sie den Clustered-Index-Scan von #Temp
, no Distinct Sort, und der Austausch verwendet jetzt eine Round-Robin-Zeilenpartitionierung):
Wenn dieses Set verfügbar ist, lautet die endgültige Abfrage:
SELECT
A.Id,
A.AssortmentId
FROM
(
SELECT
T.Assortment_Id
FROM #Temp2 AS T
GROUP BY
T.Assortment_Id
HAVING
COUNT_BIG(DISTINCT T.CustomAttrID) = @dist_ca_id
) AS DT
JOIN dbo.Assortments AS A
ON A.Id = DT.Assortment_Id
WHERE
A.AssortmentType = @asType
OPTION (RECOMPILE);
Wir könnten das COUNT_BIG(DISTINCT...
als einfaches manuell umschreiben COUNT_BIG(*)
, aber mit den neuen Schlüsselinformationen erledigt der Optimierer das für uns:
Der endgültige Plan verwendet möglicherweise einen Loop / Hash / Merge-Join, abhängig von statistischen Informationen zu den Daten, auf die ich keinen Zugriff habe. Noch eine kleine Anmerkung: Ich habe angenommen, dass ein Index wie CREATE [UNIQUE?] NONCLUSTERED INDEX IX_ ON dbo.Assortments (AssortmentType, Id, AssortmentId);
existiert.
Das Wichtigste an den endgültigen Plänen ist jedoch, dass die Schätzungen viel besser sein sollten und die komplexe Abfolge der Gruppierungsvorgänge auf ein einziges Stream-Aggregat reduziert wurde (das keinen Speicher benötigt und daher nicht auf die Festplatte übertragen werden kann).
Es ist schwer zu sagen, dass die Leistung in diesem Fall mit der zusätzlichen temporären Tabelle tatsächlich besser sein wird, aber die Schätzungen und Planoptionen sind gegenüber Änderungen des Datenvolumens und der Verteilung im Laufe der Zeit viel widerstandsfähiger. Das kann langfristig wertvoller sein als eine kleine Leistungssteigerung heute. In jedem Fall haben Sie jetzt viel mehr Informationen, auf die Sie Ihre endgültige Entscheidung stützen können.
#temp
Erstellung und Verwendung ein Problem für die Leistung sein würde, kein Gewinn. Sie speichern in einer nicht indizierten Tabelle, die nur einmal verwendet werden darf. Versuchen Sie, es vollständig zu entfernen (und ändern Sie es möglicherweisein (select id from #temp)
in eineexists
Unterabfrage.