Ich teste das Löschen von Daten aus einem Clustered Columnstore-Index.
Mir ist aufgefallen, dass der Ausführungsplan einen großen eifrigen Spool-Operator enthält:
Dies schließt mit den folgenden Eigenschaften ab:
- 60 Millionen Zeilen gelöscht
- 1.9 GiB TempDB verwendet
- 14 Minuten Ausführungszeit
- Serienplan
- 1 Auf Spule neu binden
- Geschätzte Kosten für den Scan: 364.821
Wenn ich den Schätzer zu wenig schätze, erhalte ich einen schnelleren Plan, der die Verwendung von TempDB vermeidet:
Geschätzte Scan-Kosten: 56.901
(Dies ist ein geschätzter Plan, aber die Zahlen in den Kommentaren sind korrekt.)
Interessanterweise verschwindet die Spool wieder, wenn ich die Delta-Speicher mit folgendem Befehl lösche:
ALTER INDEX IX_Clustered ON Fact.RecordedMetricsDetail REORGANIZE WITH (COMPRESS_ALL_ROW_GROUPS = ON);
Die Spool wird anscheinend nur eingeführt, wenn die Deltaspeicher mehr als einen bestimmten Schwellenwert an Seiten aufweisen.
Um die Größe der Deltaspeicher zu überprüfen, führe ich die folgende Abfrage aus, um nach In-Row-Seiten für die Tabelle zu suchen:
SELECT
SUM([in_row_used_page_count]) AS in_row_used_pages,
SUM(in_row_data_page_count) AS in_row_data_pages
FROM sys.[dm_db_partition_stats] as pstats
JOIN sys.partitions AS p
ON pstats.partition_id = p.partition_id
WHERE p.[object_id] = OBJECT_ID('Fact.RecordedMetricsDetail');
Hat der Spool-Iterator im ersten Plan einen plausiblen Nutzen? Ich muss davon ausgehen, dass es sich um eine Leistungssteigerung handelt und nicht um einen Halloween-Schutz, da seine Präsenz nicht konsistent ist.
Ich teste dies auf 2016 CTP 3.1, aber ich sehe das gleiche Verhalten auf 2014 SP1 CU3.
Ich habe ein Skript geschrieben , das Schema und die Daten erzeugt und führt Sie durch das Problem demonstriert hier .
Die Frage ist größtenteils aus Neugier über das Verhalten des Optimierers zu diesem Zeitpunkt, da ich eine Problemumgehung für das Problem habe, das die Frage ausgelöst hat (eine große, mit Spool gefüllte TempDB). Ich lösche jetzt, indem ich stattdessen den Partitionswechsel verwende.
OPTION (QUERYRULEOFF EnforceHPandAccCard)
verschwindet die Spule. Ich gehe davon aus, dass HP "Halloween Protection" ist. Der Versuch, diesen Plan mit einemUSE PLAN
Hinweis zu verwenden, schlägt jedoch fehl (ebenso wie der Versuch, den Plan aus derOPTIMIZE FOR
Problemumgehung zu verwenden)AccCard
wäre? Aufsteigende Spalte Kardinalität Kardinalität vielleicht?Antworten:
Dies hängt davon ab, was Sie als "plausibel" betrachten, aber die Antwort gemäß dem Kostenmodell lautet "Ja". Dies ist natürlich richtig, da der Optimierer immer den günstigsten Plan auswählt, den er findet.
Die eigentliche Frage ist, warum das Kostenmodell den Plan mit der Spule für so viel billiger hält als den Plan ohne. Berücksichtigen Sie geschätzte Pläne, die für eine neue Tabelle (aus Ihrem Skript) erstellt wurden, bevor dem Delta-Speicher Zeilen hinzugefügt wurden:
Die geschätzten Kosten für diesen Plan betragen 771.734 Einheiten :
Die Kosten hängen fast ausschließlich mit dem Löschen des Clustered Index zusammen, da erwartet wird, dass die Löschvorgänge zu einer großen Anzahl zufälliger E / A-Vorgänge führen. Dies ist nur die generische Logik, die für alle Datenänderungen gilt. Beispielsweise wird angenommen, dass ein ungeordneter Satz von Modifikationen an einem B-Tree-Index zu weitgehend zufälligen E / A mit damit verbundenen hohen E / A-Kosten führt.
Datenänderungspläne können aus genau diesen Kostengründen eine Sortierung enthalten, um die Zeilen in einer Reihenfolge darzustellen, die den sequenziellen Zugriff fördert. Die Auswirkung wird in diesem Fall verstärkt, da die Tabelle partitioniert ist. Tatsächlich sehr unterteilt; Ihr Skript erstellt 15.000 davon. Zufällige Aktualisierungen einer sehr partitionierten Tabelle sind besonders kostspielig, da auch der Preis für das Wechseln von Partitionen (Rowsets) während des Streams hoch ist.
Der letzte wichtige Faktor, der berücksichtigt werden muss, ist, dass die oben genannte einfache Aktualisierungsabfrage (wobei "Aktualisierung" jede Datenänderungsoperation, einschließlich eines Löschvorgangs, bedeutet) für eine Optimierung mit der Bezeichnung "Rowset-Sharing" qualifiziert ist, bei der für das Scannen und das gleiche interne Rowset verwendet wird Aktualisieren der Tabelle. Der Ausführungsplan enthält weiterhin zwei separate Operatoren, es wird jedoch nur ein Rowset verwendet.
Ich erwähne dies, weil die Möglichkeit, diese Optimierung anzuwenden, bedeutet, dass der Optimierer einen Codepfad verwendet, der die potenziellen Vorteile einer expliziten Sortierung zur Reduzierung der Kosten für zufällige E / A einfach nicht berücksichtigt . Wenn es sich bei der Tabelle um einen B-Baum handelt, ist dies sinnvoll, da die Struktur inhärent geordnet ist und die gemeinsame Nutzung des Rowsets alle potenziellen Vorteile automatisch bietet.
Die wichtige Konsequenz ist, dass die Kalkulationslogik für den Aktualisierungsoperator diesen Bestellvorteil (Förderung von sequenziellen E / A oder anderen Optimierungen) nicht berücksichtigt, wenn das zugrunde liegende Objekt ein Spaltenspeicher ist. Dies liegt daran, dass Änderungen am Spaltenspeicher nicht direkt durchgeführt werden. Sie nutzen einen Delta Store. Das Kostenmodell spiegelt daher einen Unterschied zwischen Aktualisierungen für gemeinsam genutzte Rowsets in B-Bäumen und in Spaltenspeichern wider.
In dem speziellen Fall eines (sehr!) Partitionierten Spaltenspeichers kann es dennoch von Vorteil sein, die Reihenfolge beizubehalten, da das Ausführen aller Aktualisierungen einer Partition vor dem Übergang zur nächsten aus E / A-Sicht weiterhin vorteilhaft sein kann .
Die Standardkostenlogik wird hier für Spaltenspeicher wiederverwendet, sodass ein Plan, der die Partitionsreihenfolge beibehält (jedoch nicht die Reihenfolge innerhalb jeder Partition), kostengünstiger ist. Wir können dies in der Testabfrage sehen, indem wir das undokumentierte Ablaufverfolgungsflag 2332 verwenden, um eine sortierte Eingabe für den Aktualisierungsoperator zu erfordern. Dies setzt die
DMLRequestSort
Eigenschaft bei der Aktualisierung auf true und zwingt das Optimierungsprogramm, einen Plan zu erstellen, der alle Zeilen für eine Partition bereitstellt, bevor zum nächsten übergegangen wird:Die geschätzten Kosten für diesen Plan sind mit 52.5174 Einheiten sehr viel niedriger :
Diese Kostensenkung ist allesamt auf die niedrigeren geschätzten E / A-Kosten bei der Aktualisierung zurückzuführen. Der eingeführte Spool führt keine nützliche Funktion aus, außer dass er die Ausgabe in der Partitionsreihenfolge garantieren kann, wie dies durch das Update mit erforderlich ist
DMLRequestSort = true
(der serielle Scan eines Spaltenspeicherindex kann diese Garantie nicht bieten). Die Kosten der Spule selbst werden als relativ niedrig angesehen, insbesondere im Vergleich zu der (wahrscheinlich unrealistischen) Kostensenkung bei der Aktualisierung.Die Entscheidung, ob eine geordnete Eingabe an den Aktualisierungsoperator erforderlich ist, wird sehr früh bei der Abfrageoptimierung getroffen. Die in dieser Entscheidung verwendeten Heuristiken wurden nie dokumentiert, können jedoch durch Ausprobieren ermittelt werden. Es scheint, dass die Größe von Delta-Speichern ein Input für diese Entscheidung ist. Einmal getroffen, ist die Auswahl für die Abfragezusammenstellung permanent. Kein
USE PLAN
Hinweis wird erfolgreich sein: Das Ziel des Plans hat entweder Eingaben für das Update angeordnet oder nicht.Es gibt eine andere Möglichkeit, einen kostengünstigen Plan für diese Abfrage zu erhalten, ohne die Kardinalitätsschätzung künstlich einzuschränken. Eine ausreichend niedrige Schätzung, um den Spool zu vermeiden, führt wahrscheinlich dazu, dass DMLRequestSort falsch ist, was aufgrund der erwarteten zufälligen E / A zu sehr hohen geschätzten Planungskosten führt. Eine Alternative besteht darin, das Ablaufverfolgungsflag 8649 (paralleler Plan) in Verbindung mit 2332 (DMLRequestSort = true) zu verwenden:
Dies führt zu einem Plan, der einen parallelen Scan pro Partition im Batch-Modus und einen ordnungserhaltenden (zusammengeführten) Austausch von Gather-Streams verwendet:
Abhängig von der Laufzeiteffektivität der Partitionsreihenfolge auf Ihrer Hardware kann dies die beste Leistung bringen. Allerdings sind große Änderungen keine gute Idee für den Spaltenspeicher, sodass die Idee des Partitionswechsels mit ziemlicher Sicherheit besser ist. Wenn Sie mit den langen Kompilierungszeiten und den ungewöhnlichen Planoptionen fertig werden, die häufig bei partitionierten Objekten zu finden sind - insbesondere, wenn die Anzahl der Partitionen groß ist.
Die Kombination vieler, relativ neuer Features, insbesondere in der Nähe ihrer Grenzen, ist eine gute Möglichkeit, schlechte Ausführungspläne zu erhalten. Die Tiefe der Optimiererunterstützung nimmt im Laufe der Zeit tendenziell zu, aber die Verwendung von 15.000 Partitionen des Spaltenspeichers bedeutet wahrscheinlich immer, dass Sie in interessanten Zeiten leben.
quelle