Diese Frage wurde von Jake Manske auf #sqlhelp gepostet und von Erik Darling darauf aufmerksam gemacht.
Ich kann mich nicht erinnern, jemals ein Leistungsproblem mit sys.partitions
gehabt zu haben . Mein anfänglicher Gedanke (echo von Joey D'Antoni) war, dass ein Filter auf der data_compression
Spalte sollte Vermeiden Sie den redundanten Scan und reduzieren Sie die Abfragelaufzeit um etwa die Hälfte. Dieses Prädikat wird jedoch nicht heruntergedrückt, und der Grund dafür erfordert ein wenig Auspacken.
Warum ist sys.partitions langsam?
Wenn Sie sich die Definition für sys.partitions
ansehen , es ist im Grunde das, was Jake beschrieben hat – ein UNION ALL
aller Columnstore- und Rowstore-Partitionen mit DREI explizite Verweise auf sys.sysrowsets
(hier abgekürzte Quelle):
CREATE VIEW sys.partitions AS WITH partitions_columnstore(...cols...) AS ( SELECT ...cols..., cmprlevel AS data_compression ... FROM sys.sysrowsets rs OUTER APPLY OpenRowset(TABLE ALUCOUNT, rs .rowsetid, 0, 0, 0) ct-------- *** ^^^^^^^^^^^^^^^ *** LEFT JOIN sys.syspalvalues cl ... WHERE .. .sysconv(bit, rs.status &0x00010000) =1 – Nur Columnstore-Basisindizes berücksichtigen ), partitions_rowstore(...cols...) AS ( SELECT ...cols..., cmprlevel AS data_compression ... FROM sys.sysrowsets rs -------- *** ^^^^^^^^^^^^^^ *** LEFT JOIN sys.syspalvalues cl ... WHERE ... sysconv(bit, rs .status &0x00010000) =0 -- Columnstore-Basisindizes und verwaiste Zeilen ignorieren ) SELECT ...cols... from partitions_rowstore p OUTER APPLY OpenRowset(TABLE ALUCOUNT, p.partition_id, 0, 0, p.object_id) ct union all SELECT ...cols... FROM partitions_columnstore as P1 LEFT JOIN (SELECT ...cols... FROM sys.sysrowsets rs OUTER APP LY OpenRowset(TABLE ALUCOUNT, rs.rowsetid, 0, 0, 0) ct------- *** ^^^^^^^^^^^^^^^ *** ) ...Diese Ansicht scheint zusammengeschustert zu sein, wahrscheinlich aufgrund von Bedenken hinsichtlich der Abwärtskompatibilität. Es könnte sicherlich effizienter umgeschrieben werden, insbesondere um nur auf die
sys.sysrowsets
zu verweisen undTABLE ALUCOUNT
Objekte einmal. Aber daran können Sie und ich im Moment nicht viel ändern.Die Spalte
cmprlevel
kommt vonsys.sysrowsets
(ein Alias-Präfix auf der Spaltenreferenz wäre hilfreich gewesen). Sie würden hoffen, dass ein Prädikat gegen eine dortige Spalte logischerweise vor einemOUTER APPLY
auftritt und könnte einen der Scans verhindern, aber das passiert nicht. Ausführen der folgenden einfachen Abfrage:SELECT * FROM sys.partitions AS p INNER JOIN sys.objects AS o ON p.object_id =o.object_id WHERE o.is_ms_shipped =0;Ergibt den folgenden Plan, wenn Columnstore-Indizes in den Datenbanken vorhanden sind (zum Vergrößern klicken):
Planen Sie sys.partitions mit vorhandenen Columnstore-Indizes
Und der folgende Plan, wenn es keine gibt (zum Vergrößern anklicken):
Planen Sie sys.partitions ohne vorhandene Columnstore-Indizes
Dies sind die gleichen geschätzten Pläne, aber SentryOne Plan Explorer kann hervorheben, wenn eine Operation zur Laufzeit übersprungen wird. Dies geschieht beim dritten Scan im letzteren Fall, aber ich weiß nicht, ob es eine Möglichkeit gibt, die Anzahl der Laufzeitscans weiter zu reduzieren. der zweite Scan erfolgt auch dann, wenn die Abfrage null Zeilen zurückgibt.
In Jakes Fall hat er viel von Objekten, so dass es auffällig, schmerzhaft und einmal zu viel ist, diesen Scan sogar zweimal durchzuführen. Und ehrlich gesagt weiß ich nicht, ob
TABLE ALUCOUNT
, ein interner und undokumentierter Loopback-Aufruf, muss einige dieser größeren Objekte ebenfalls mehrfach scannen.Als ich auf die Quelle zurückblickte, fragte ich mich, ob es ein anderes Prädikat gab, das an die Ansicht weitergegeben werden könnte, das die Planform erzwingen könnte, aber ich glaube wirklich nicht, dass es irgendetwas gibt, das einen Einfluss haben könnte.
Wird eine andere Ansicht funktionieren?
Wir könnten jedoch eine ganz andere Sichtweise versuchen. Ich habe nach anderen Ansichten gesucht, die Verweise auf beide
sys.sysrowsets
enthielten undALUCOUNT
, und es gibt mehrere, die in der Liste auftauchen, aber nur zwei sind vielversprechend:sys.internal_partitions
undsys.system_internals_partitions
.sys.interne_Partitionen
Ich habe
sys.internal_partitions
ausprobiert zuerst:SELECT * FROM sys.internal_partitions AS p INNER JOIN sys.objects AS o ON p.object_id =o.object_id WHERE o.is_ms_shipped =0;Aber der Plan war nicht viel besser (zum Vergrößern anklicken):
Planen Sie sys.internal_partitions
Es gibt nur zwei Scans gegen
sys.sysrowsets
dieses Mal, aber die Scans sind ohnehin irrelevant, da die Abfrage nicht annähernd die Zeilen erzeugt, an denen wir interessiert sind. Wir sehen nur Zeilen für Columnstore-bezogene Objekte (wie in der Dokumentation angegeben).sys.system_internals_partitions
Versuchen wir es mit
sys.system_internals_partitions
. Ich bin diesbezüglich etwas vorsichtig, weil es nicht unterstützt wird (siehe die Warnung hier), aber haben Sie einen Moment Geduld:SELECT * FROM sys.system_internals_partitions AS p INNER JOIN sys.objects AS o ON p.object_id =o.object_id WHERE o.is_ms_shipped =0;In der Datenbank mit Columnstore-Indizes gibt es einen Scan gegen
sys.sysschobjs
, aber jetzt nur noch eine Scan gegensys.sysrowsets
(zum Vergrößern anklicken):Planen Sie sys.system_internals_partitions mit vorhandenen Columnstore-Indizes
Wenn wir dieselbe Abfrage in der Datenbank ohne Columnstore-Indizes ausführen, ist der Plan noch einfacher, mit einer Suche nach
sys.sysschobjs
(zum Vergrößern anklicken):Planen Sie sys.system_internals_partitions ohne vorhandene Columnstore-Indizes
Dies ist jedoch nicht ganz das, wonach wir suchen, oder zumindest nicht ganz das, wonach Jake gesucht hat, da es auch Artefakte aus Columnstore-Indizes enthält. Wenn wir diese Filter hinzufügen, stimmt die tatsächliche Ausgabe jetzt mit unserer früheren, viel teureren Abfrage überein:
SELECT * FROM sys.system_internals_partitions AS p INNER JOIN sys.objects AS o ON p.object_id =o.object_id WHERE o.is_ms_shipped =0 AND p.is_columnstore =0 AND p.is_orphaned =0;Als Bonus der Scan gegen
sys.sysschobjs
ist sogar in der Datenbank mit Columnstore-Objekten zu einer Suche geworden. Die meisten von uns werden diesen Unterschied nicht bemerken, aber wenn Sie sich in einem Szenario wie dem von Jake befinden, könnten Sie es tun (zum Vergrößern klicken):Einfacher Plan für sys.system_internals_partitions, mit zusätzlichen Filtern
sys.system_internals_partitions
macht einen anderen Satz von Spalten verfügbar alssys.partitions
(Einige sind völlig anders, andere haben neue Namen). Wenn Sie also die Ausgabe nachgelagert verbrauchen, müssen Sie diese anpassen. Sie sollten auch überprüfen, ob alle gewünschten Informationen über Rowstore-, speicheroptimierte und Columnstore-Indizes zurückgegeben werden, und vergessen Sie diese lästigen Haufen nicht. Und seien Sie schließlich bereit, dies
wegzulassen ininternals
viele, viele Male.Schlussfolgerung
Wie ich oben erwähnt habe, wird diese Systemansicht nicht offiziell unterstützt, daher kann sich ihre Funktionalität jederzeit ändern; es könnte auch unter die Dedicated Administrator Connection (DAC) verschoben oder ganz aus dem Produkt entfernt werden. Fühlen Sie sich frei, diesen Ansatz zu verwenden, wenn
sys.partitions
funktioniert nicht gut für Sie, aber bitte stellen Sie sicher, dass Sie einen Backup-Plan haben. Und stellen Sie sicher, dass es als Regressionstest dokumentiert wird, wenn Sie mit dem Testen zukünftiger Versionen von SQL Server beginnen, oder nach einem Upgrade, nur für den Fall.