Database
 sql >> Datenbank >  >> RDS >> Database

Leistung von sys.partitions

sys.partitions scheint ein UNION ALL aus zwei Resultsets (Row Store und Columnstore) zu sein, und die meisten meiner Abfragen führen zu zwei Scans von sysrowsets. Gibt es einen Filter, den ich auf eine Abfrage von sys.partitions anwenden kann, wenn ich weiß, dass die Zeile, nach der ich suche, rowstore ist?

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 und TABLE ALUCOUNT Objekte einmal. Aber daran können Sie und ich im Moment nicht viel ändern.

Die Spalte cmprlevel kommt von sys.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 einem OUTER 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 und ALUCOUNT , und es gibt mehrere, die in der Liste auftauchen, aber nur zwei sind vielversprechend:sys.internal_partitions und sys.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 gegen sys.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 als sys.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, die s wegzulassen in internals 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.