Dette spørgsmål blev sendt til #sqlhelp af Jake Manske, og det blev gjort mig opmærksom på af Erik Darling.
Jeg kan ikke huske, at jeg nogensinde har haft et ydeevneproblem med sys.partitions
. Min første tanke (som gentaget af Joey D'Antoni) var, at et filter på data_compression
kolonne skal undgå den overflødige scanning, og reducer forespørgselskørselstiden med cirka det halve. Dette prædikat bliver dog ikke presset ned, og grunden til det kræver lidt udpakning.
Hvorfor er sys.partitions langsomme?
Hvis du ser på definitionen for sys.partitions
, det er dybest set, hvad Jake beskrev – en UNION ALL
af alle columnstore- og rowstore-partitionerne med TRE eksplicitte referencer til sys.sysrowsets
(forkortet kilde her):
CREATE VIEW sys.partitions AS WITH partitions_columnstore(...cols...) AS (VÆLG ...cols..., cmprlevel AS data_compression ... FRA sys.sysrowsets rs YDRE ANVEND OpenRowset(TABEL ALUCOUNT, rs) .rowsetid, 0, 0, 0) ct-------- *** ^^^^^^^^^^^^^^ *** VENSTRE JOIN sys.syspalvalues cl ... HVOR .. . sysconv(bit, rs.status &0x00010000) =1 -- Overvej kun kolonnelagerbaseindekser ), partitions_rowstore(...cols...) AS ( SELECT ...cols..., cmprlevel AS data_compression ... FROM sys.sysrowsets rs -------- *** ^^^^^^^^^^^^^^^ *** VENSTRE JOIN sys.syspalvalues cl ... HVOR ... sysconv(bit, rs .status &0x00010000) =0 -- Ignorer kolonnelagerbaseindekser og forældreløse rækker. ) VÆLG ...cols... fra partitions_rowstore p YDRE ANVEND OpenRowset(TABEL ALUCOUNT, p.partition_id, 0, 0, p.object_id) ct union alle SELECT ...cols... FRA partitions_columnstore som P1 LEFT JOIN (VÆLG ...cols... FRA sys.sysrowsets rs YDRE APP LY OpenRowset(TABEL ALUCOUNT, rs.rowsetid, 0, 0, 0) ct------ *** ^^^^^^^^^^^^^^ *** ) ...Dette synspunkt virker brostenset sammen, sandsynligvis på grund af bagudkompatibilitetsproblemer. Det kunne helt sikkert omskrives for at være mere effektivt, især til kun at henvise til
sys.sysrowsets
ogTABLE ALUCOUNT
genstande én gang. Men der er ikke meget, du eller jeg kan gøre ved det lige nu.Kolonnen
cmprlevel
kommer frasys.sysrowsets
(et aliaspræfiks på kolonnehenvisningen ville have været nyttigt). Du ville håbe, at et prædikat mod en kolonne der logisk ville ske før nogenOUTER APPLY
og kunne forhindre en af scanningerne, men det er ikke det, der sker. Kører følgende simple forespørgsel:VÆLG * FRA sys.partitions AS p INNER JOIN sys.objects AS o ON p.object_id =o.object_id WHERE o.is_ms_shipped =0;Giver følgende plan, når der er kolonnelagerindekser i databaserne (klik for at forstørre):
Plan for sys.partitions, med kolonnelagerindekser til stede
Og følgende plan, når der ikke er (klik for at forstørre):
Plan for sys.partitions, uden kolonnelagerindekser til stede
Disse er den samme estimerede plan, men SentryOne Plan Explorer er i stand til at fremhæve, når en operation springes over under kørsel. Dette sker for den tredje scanning i sidstnævnte tilfælde, men jeg ved ikke, at der er nogen måde at reducere antallet af runtime-scanninger yderligere; den anden scanning sker, selv når forespørgslen returnerer nul rækker.
I Jakes tilfælde har han en masse af genstande, så at udføre denne scanning endda to gange er mærkbar, smertefuld og én gang for meget. Og helt ærligt ved jeg ikke om
TABLE ALUCOUNT
, et internt og udokumenteret loopback-kald, skal også scanne nogle af disse større objekter flere gange.Når jeg så tilbage på kilden, spekulerede jeg på, om der var noget andet prædikat, der kunne overføres til synspunktet, der kunne tvinge planens form, men jeg tror virkelig ikke, der er noget, der kunne have en indflydelse.
Vil en anden visning fungere?
Vi kunne dog prøve en helt anden opfattelse. Jeg ledte efter andre visninger, der indeholdt referencer til begge
sys.sysrowsets
ogALUCOUNT
, og der er flere, der dukker op på listen, men kun to er lovende:sys.internal_partitions
ogsys.system_internals_partitions
.sys.internal_partitions
Jeg prøvede
sys.internal_partitions
først:VÆLG * FRA sys.internal_partitions AS p INNER JOIN sys.objects AS o ON p.object_id =o.object_id WHERE o.is_ms_shipped =0;Men planen var ikke meget bedre (klik for at forstørre):
Plan for sys.internal_partitions
Der er kun to scanninger mod
sys.sysrowsets
denne gang, men scanningerne er alligevel irrelevante, fordi forespørgslen ikke kommer i nærheden af at producere de rækker, vi er interesserede i. Vi ser kun rækker for columnstore-relaterede objekter (som det fremgår af dokumentationen).sys.system_internals_partitions
Lad os prøve
sys.system_internals_partitions
. Jeg er lidt forsigtig med dette, fordi det ikke understøttes (se advarslen her), men bær over med mig et øjeblik:VÆLG * FRA 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;I databasen med kolonnelagerindekser er der en scanning mod
sys.sysschobjs
, men nu kun én scan modsys.sysrowsets
(klik for at forstørre):Plan for sys.system_internals_partitions, med kolonnelagerindekser til stede
Hvis vi kører den samme forespørgsel i databasen uden kolonnelagerindekser, er planen endnu enklere, med en søgning mod
sys.sysschobjs
(klik for at forstørre):Plan for sys.system_internals_partitions, uden kolonnelagerindekser til stede
Dette er dog ikke helt hvad vi leder efter, eller i det mindste ikke helt hvad Jake var ude efter, fordi det også inkluderer artefakter fra columnstore-indekser. Hvis vi tilføjer disse filtre, svarer det faktiske output nu til vores tidligere, meget dyrere forespørgsel:
VÆLG * FRA sys.system_internals_partitions AS p INNER JOIN sys.objects AS o ON p.object_id =o.object_id HVOR o.is_ms_shipped =0 OG p.is_columnstore =0 OG p.is_orphaned =0;Som en bonus kan scanningen mod
sys.sysschobjs
er blevet en søgning selv i databasen med columnstore-objekter. De fleste af os vil ikke bemærke den forskel, men hvis du er i et scenarie som Jakes, kan du bare (klik for at forstørre):Enklere plan for sys.system_internals_partitions, med yderligere filtre
sys.system_internals_partitions
viser et andet sæt kolonner endsys.partitions
(nogle er helt anderledes, andre har nye navne), så hvis du forbruger output downstream, bliver du nødt til at justere for dem. Du vil også gerne bekræfte, at den returnerer al den information, du ønsker på tværs af rowstore, hukommelsesoptimerede og columnstore-indekser, og glem ikke de irriterende dynger. Og vær endelig klar til at udelades
iinternals
mange, mange gange.Konklusion
Som jeg nævnte ovenfor, er denne systemvisning ikke officielt understøttet, så dens funktionalitet kan ændres til enhver tid; den kan også flyttes under DAC (Dedicated Administrator Connection) eller fjernes helt fra produktet. Du er velkommen til at bruge denne tilgang, hvis
sys.partitions
fungerer ikke godt for dig, men sørg venligst for at have en backup-plan. Og sørg for, at det er dokumenteret som noget, du regressionstester, når du begynder at teste fremtidige versioner af SQL Server, eller efter du har opgraderet, for en sikkerheds skyld.