Deze vraag is door Jake Manske op #sqlhelp geplaatst en onder mijn aandacht gebracht door Erik Darling.
Ik kan me niet herinneren ooit een prestatieprobleem te hebben gehad met sys.partitions
. Mijn eerste gedachte (gevolgd door Joey D'Antoni) was dat een filter op de data_compression
kolom moet vermijd de redundante scan en verkort de runtime van query's met ongeveer de helft. Dit predikaat wordt echter niet naar beneden gedrukt, en de reden waarom vereist een beetje uitpakken.
Waarom is sys.partitions traag?
Als je kijkt naar de definitie voor sys.partitions
, het is eigenlijk wat Jake beschreef - een UNION ALL
van alle columnstore- en rowstore-partities, met DRIE expliciete verwijzingen naar sys.sysrowsets
(afgekorte bron hier):
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 -- Consider only columnstore base indexes ), 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 -- Ignore columnstore base indexes and orphaned rows. ) 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 APPLY OpenRowset(TABLE ALUCOUNT, rs.rowsetid, 0, 0, 0) ct ------- *** ^^^^^^^^^^^^^^ *** ) ...
Deze visie lijkt in elkaar geflanst, waarschijnlijk vanwege zorgen over achterwaartse compatibiliteit. Het kan zeker worden herschreven om efficiënter te zijn, met name om alleen te verwijzen naar de sys.sysrowsets
en TABLE ALUCOUNT
objecten een keer. Maar daar kunnen jij of ik op dit moment niet veel aan doen.
De kolom cmprlevel
komt van sys.sysrowsets
(een alias-prefix op de kolomverwijzing zou handig zijn geweest). Je zou hopen dat een predikaat tegen een kolom logischerwijs zou gebeuren vóór een OUTER APPLY
en kan een van de scans voorkomen, maar dat is niet wat er gebeurt. Voer de volgende eenvoudige query uit:
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;
Levert het volgende plan op als er columnstore-indexen in de databases zijn (klik om te vergroten):
Plan voor sys.partitions, met columnstore-indexen aanwezig
En het volgende plan als die er niet zijn (klik om te vergroten):
Plan voor sys.partitions, zonder dat er columnstore-indexen aanwezig zijn
Dit is hetzelfde geschatte abonnement, maar SentryOne Plan Explorer kan tijdens runtime markeren wanneer een bewerking wordt overgeslagen. Dit gebeurt voor de derde scan in het laatste geval, maar ik weet niet of er een manier is om dat aantal runtime-scans verder te verminderen; de tweede scan gebeurt zelfs als de query nul rijen retourneert.
In het geval van Jake heeft hij veel van objecten, dus deze scan zelfs twee keer uitvoeren is merkbaar, pijnlijk en een keer te veel. En eerlijk gezegd weet ik niet of TABLE ALUCOUNT
, een interne en ongedocumenteerde loopback-aanroep, moet ook enkele van deze grotere objecten meerdere keren scannen.
Terugkijkend naar de bron, vroeg ik me af of er een ander predikaat was dat aan het uitzicht kon worden doorgegeven dat de vorm van het plan zou kunnen dwingen, maar ik denk echt niet dat er iets is dat een impact zou kunnen hebben.
Werkt een andere weergave?
We zouden echter een heel andere kijk kunnen proberen. Ik zocht naar andere weergaven die verwijzingen bevatten naar beide sys.sysrowsets
en ALUCOUNT
, en er zijn er meerdere die in de lijst verschijnen, maar slechts twee zijn veelbelovend:sys.internal_partitions
en sys.system_internals_partitions
.
sys.internal_partitions
Ik probeerde sys.internal_partitions
eerst:
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;
Maar het plan was niet veel beter (klik om te vergroten):
Plan voor sys.internal_partitions
Er zijn slechts twee scans tegen sys.sysrowsets
deze keer, maar de scans zijn sowieso niet relevant omdat de zoekopdracht niet in de buurt komt van het produceren van de rijen waarin we geïnteresseerd zijn. We zien alleen rijen voor columnstore-gerelateerde objecten (zoals de documentatie aangeeft).
sys.system_internals_partitions
Laten we sys.system_internals_partitions
proberen . Ik ben hier een beetje huiverig voor, omdat het niet wordt ondersteund (zie de waarschuwing hier), maar heb even 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 de database met columnstore-indexen wordt gescand op sys.sysschobjs
, maar nu nog maar één scan tegen sys.sysrowsets
(klik om te vergroten):
Plan voor sys.system_internals_partitions, met columnstore-indexen aanwezig
Als we dezelfde query uitvoeren in de database zonder columnstore-indexen, is het plan nog eenvoudiger, met een zoekopdracht tegen sys.sysschobjs
(klik om te vergroten):
Plan voor sys.system_internals_partitions, zonder aanwezige columnstore-indexen
Dit is echter niet heel wat we zoeken, of in ieder geval niet helemaal waar Jake naar op zoek was, omdat het ook artefacten uit columnstore-indexen bevat. Als we deze filters toevoegen, komt de werkelijke uitvoer nu overeen met onze eerdere, veel duurdere zoekopdracht:
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 de scan tegen sys.sysschobjs
is een zoektocht geworden, zelfs in de database met columnstore-objecten. De meesten van ons zullen dat verschil niet merken, maar als je in een scenario zoals dat van Jake zit, zou je misschien (klik om te vergroten):
Eenvoudiger plan voor sys.system_internals_partitions, met extra filters
sys.system_internals_partitions
toont een andere set kolommen dan sys.partitions
(sommige zijn compleet anders, andere hebben nieuwe namen) dus als je de output stroomafwaarts verbruikt, moet je je daarvoor aanpassen. U zult ook willen valideren dat het alle gewenste informatie retourneert in rowstore-, geheugen-geoptimaliseerde en columnstore-indexen, en vergeet die vervelende hopen niet. En tot slot, wees klaar om de s
. weg te laten in internals
vele, vele malen.
Conclusie
Zoals ik hierboven al zei, wordt deze systeemweergave niet officieel ondersteund, dus de functionaliteit kan op elk moment veranderen; het kan ook worden verplaatst onder de Dedicated Administrator Connection (DAC) of helemaal uit het product worden verwijderd. Voel je vrij om deze aanpak te gebruiken als sys.partitions
werkt niet goed voor u, maar zorg er alstublieft voor dat u een back-upplan heeft. En zorg ervoor dat het wordt gedocumenteerd als iets dat u regressietests uitvoert wanneer u toekomstige versies van SQL Server gaat testen, of nadat u een upgrade hebt uitgevoerd, voor het geval dat.