Den här frågan postades till #sqlhelp av Jake Manske, och den uppmärksammades av Erik Darling.
Jag kan inte minnas att jag någonsin haft ett prestandaproblem med sys.partitions
. Min första tanke (som upprepades av Joey D'Antoni) var att ett filter på data_compression
kolumnen bör undvik den redundanta skanningen och minska söktiden med ungefär hälften. Det här predikatet trycks dock inte ner, och anledningen till det kräver lite uppackning.
Varför är sys.partitions långsam?
Om du tittar på definitionen för sys.partitions
, det är i princip vad Jake beskrev – en UNION ALL
av alla columnstore- och rowstore-partitioner, med TRE explicita referenser till sys.sysrowsets
(förkortad källa här):
SKAPA VISNING sys.partitions AS WITH partitions_columnstore(...cols...) AS ( VÄLJ ...cols..., cmprlevel AS data_compression ... FRÅN sys.sysrowsets rs YTTRE ANVÄND OpenRowset(TABELL ALUCOUNT, rs .rowsetid, 0, 0, 0) ct-------- *** ^^^^^^^^^^^^^^ *** VÄNSTER JOIN sys.syspalvalues cl ... VAR .. . sysconv(bit, rs.status &0x00010000) =1 -- Tänk bara på kolumnlagerbasindex ), partitions_rowstore(...cols...) AS ( SELECT ...cols..., cmprlevel AS data_compression ... FROM sys.sysrowsets rs -------- *** ^^^^^^^^^^^^^^^ *** VÄNSTER JOIN sys.syspalvalues cl ... WHERE ... sysconv(bit, rs .status &0x00010000) =0 -- Ignorera kolumnlagerbasindex och föräldralösa rader. ) VÄLJ ...kol... från partitions_rowstore p OUTTER APPLY OpenRowset(TABELL ALUCOUNT, p.partition_id, 0, 0, p.object_id) ct union alla SELECT ...cols... FROM partitions_columnstore som P1 LEFT JOIN (VÄLJ ...cols... FRÅN sys.sysrowsets rs YTTRE APPEN LY OpenRowset(TABELL ALUCOUNT, rs.rowsetid, 0, 0, 0) ct------ *** ^^^^^^^^^^^^^^ *** ) ...Den här uppfattningen tycks vara sammansatt, förmodligen på grund av bakåtkompatibilitetsproblem. Det skulle säkert kunna skrivas om för att bli mer effektivt, särskilt för att bara referera till
sys.sysrowsets
ochTABLE ALUCOUNT
föremål en gång. Men det finns inte mycket du eller jag kan göra åt det just nu.Kolumnen
cmprlevel
kommer frånsys.sysrowsets
(ett aliasprefix på kolumnreferensen skulle ha varit till hjälp). Du skulle hoppas att ett predikat mot en kolumn där logiskt sett skulle hända innan någonOUTER APPLY
och skulle kunna förhindra en av skanningarna, men det är inte vad som händer. Kör följande enkla fråga:VÄLJ * FRÅN sys.partitions AS p INNER JOIN sys.objects AS o ON p.object_id =o.object_id WHERE o.is_ms_shipped =0;Ger följande plan när det finns kolumnlagerindex i databaserna (klicka för att förstora):
Planera för sys.partitions, med columnstore-index närvarande
Och följande plan när det inte finns (klicka för att förstora):
Planera för sys.partitions, utan kolumnlagerindex närvarande
Dessa är samma beräknade plan, men SentryOne Plan Explorer kan markera när en operation hoppas över under körning. Detta händer för den tredje skanningen i det senare fallet, men jag vet inte att det finns något sätt att minska antalet runtime-skanningar ytterligare; den andra skanningen sker även när frågan returnerar noll rader.
I Jakes fall har han mycket av objekt, så att utföra denna skanning ens två gånger är märkbart, smärtsamt och en gång för mycket. Och ärligt talat vet jag inte om
TABLE ALUCOUNT
, ett internt och odokumenterat loopback-anrop, måste också skanna några av dessa större objekt flera gånger.När jag tittade tillbaka på källan undrade jag om det fanns något annat predikat som skulle kunna överföras till åsikten som skulle kunna tvinga fram planformen, men jag tror verkligen inte att det finns något som kan påverka.
Kommer en annan vy att fungera?
Vi skulle dock kunna prova en helt annan syn. Jag letade efter andra vyer som innehöll referenser till båda
sys.sysrowsets
ochALUCOUNT
, och det finns flera som dyker upp i listan, men bara två är lovande:sys.internal_partitions
ochsys.system_internals_partitions
.sys.internal_partitions
Jag försökte
sys.internal_partitions
först:VÄLJ * FRÅN 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 inte mycket bättre (klicka för att förstora):
Planera för sys.internal_partitions
Det finns bara två skanningar mot
sys.sysrowsets
denna gång, men skanningarna är ändå irrelevanta eftersom frågan inte kommer i närheten av att producera de rader vi är intresserade av. Vi ser bara rader för columnstore-relaterade objekt (som dokumentationen anger).sys.system_internals_partitions
Låt oss prova
sys.system_internals_partitions
. Jag är lite försiktig med detta, eftersom det inte stöds (se varningen här), men tål mig ett ögonblick:VÄLJ * FRÅN 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 kolumnlagerindex finns en skanning mot
sys.sysschobjs
, men nu bara en skanna motsys.sysrowsets
(klicka för att förstora):Planera för sys.system_internals_partitions, med columnstore-index närvarande
Om vi kör samma fråga i databasen utan kolumnlagerindex är planen ännu enklare, med en sökning mot
sys.sysschobjs
(klicka för att förstora):Planera för sys.system_internals_partitions, utan kolumnlagerindex närvarande
Detta är dock inte helt vad vi är ute efter, eller åtminstone inte riktigt vad Jake var ute efter, eftersom det också inkluderar artefakter från kolumnbutiksindex. Om vi lägger till dessa filter, matchar den faktiska utdata nu vår tidigare, mycket dyrare fråga:
VÄLJ * FRÅN 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 OCH p.is_columnstore =0 OCH p.is_orphaned =0;Som en bonus, skanningen mot
sys.sysschobjs
har blivit ett sök även i databasen med columnstore-objekt. De flesta av oss kommer inte att märka den skillnaden, men om du befinner dig i ett scenario som Jakes, kan du bara (klicka för att förstora):Enklare plan för sys.system_internals_partitions, med ytterligare filter
sys.system_internals_partitions
exponerar en annan uppsättning kolumner änsys.partitions
(vissa är helt annorlunda, andra har nya namn) så om du konsumerar utdata nedströms måste du anpassa dig efter dem. Du vill också verifiera att den returnerar all information du vill ha över index för rowstore, minnesoptimerade och columnstore, och glöm inte de där irriterande högarna. Och slutligen, var redo att utelämnas
iinternals
många, många gånger.Slutsats
Som jag nämnde ovan stöds inte denna systemvy officiellt, så dess funktionalitet kan ändras när som helst; den kan också flyttas under DAC (Dedicated Administrator Connection), eller tas bort från produkten helt och hållet. Använd gärna denna metod om
sys.partitions
fungerar inte bra för dig, men se till att du har en reservplan. Och se till att det är dokumenterat som något du regressionstestar när du börjar testa framtida versioner av SQL Server, eller efter att du har uppgraderat, för säkerhets skull.