sql >> Database >  >> RDS >> Database

Prestaties van sys.partitions

sys.partitions lijkt een UNION van ALLE twee resultaatsets (rijopslag en kolomarchief) en de meeste van mijn zoekopdrachten resulteren in twee scans van sysrowsets. Is er een filter dat ik op een query van sys.partitions kan zetten als ik weet dat de rij die ik zoek rowstore is?

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.


  1. MySQL:@variabele versus variabele. Wat is het verschil?

  2. TO_TIMESTAMP() Functie in Oracle

  3. FOUT 1044 (42000):Toegang geweigerd voor gebruiker ''@'localhost' tot database 'db'

  4. Psycopg2-afbeelding niet gevonden