Database
 sql >> Baza danych >  >> RDS >> Database

Wydajność partycji sys.

sys.partitions wydaje się być UNION ALL z dwóch zestawów wyników (magazyn wierszy i magazyn kolumn), a większość moich zapytań skutkuje dwoma skanami zestawów sysrowset. Czy jest jakiś filtr, który mogę umieścić w zapytaniu sys.partitions, jeśli wiem, że wiersz, którego szukam, to rowstore?

To pytanie zostało wysłane do #sqlhelp przez Jake'a Manske, a zwrócił moją uwagę Erik Darling.

Nie przypominam sobie, żebym kiedykolwiek miał problem z wydajnością z sys.partitions . Moją pierwszą myślą (wtórował Joey D'Antoni) było to, że filtr na data_compression kolumna powinna unikaj nadmiarowego skanowania i skróć czas wykonywania zapytań o około połowę. Jednak ten predykat nie jest spychany w dół i powód, dla którego wymaga trochę rozpakowania.

Dlaczego sys.partitions działa wolno?

Jeśli spojrzysz na definicję sys.partitions , jest to w zasadzie to, co opisał Jake – UNION ALL wszystkich partycji magazynu kolumn i magazynu wierszy, z TRZY wyraźne odniesienia do sys.sysrowsets (źródło skrócone tutaj):

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
------- *** ^^^^^^^^^^^^^^ ***
      ) ...

Ten pogląd wydaje się sklecony, prawdopodobnie z powodu obaw o kompatybilność wsteczną. Z pewnością można by go przepisać, aby był bardziej wydajny, w szczególności, aby odwoływać się tylko do sys.sysrowsets i TABLE ALUCOUNT przedmioty raz. Ale teraz niewiele możemy z tym zrobić.

Kolumna cmprlevel pochodzi z sys.sysrowsets (przydatny byłby prefiks aliasu w odwołaniu do kolumny). Mógłbyś mieć nadzieję, że orzeczenie przeciwko kolumnie tam logicznie wydarzy się przed jakimkolwiek OUTER APPLY i może zapobiec jednemu ze skanów, ale tak się nie dzieje. Uruchamiam następujące proste zapytanie:

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;

Daje następujący plan, gdy w bazach danych znajdują się indeksy magazynu kolumn (kliknij, aby powiększyć):

Plan dla sys.partitions z obecnymi indeksami magazynu kolumn

I następujący plan, gdy go nie ma (kliknij, aby powiększyć):

Plan dla sys.partitions bez indeksów magazynu kolumn

Są to ten sam szacunkowy plan, ale SentryOne Plan Explorer jest w stanie podświetlić, kiedy operacja jest pomijana w czasie wykonywania. Dzieje się tak w przypadku trzeciego skanowania w tym drugim przypadku, ale nie wiem, czy istnieje sposób na dalsze zmniejszenie liczby skanów w czasie wykonywania; drugie skanowanie ma miejsce nawet wtedy, gdy zapytanie zwraca zero wierszy.

W przypadku Jake'a ma dużo obiektów, więc wykonanie tego skanu nawet dwa razy jest zauważalne, bolesne i jednorazowo za dużo. I szczerze mówiąc nie wiem czy TABLE ALUCOUNT , wewnętrzne i nieudokumentowane wywołanie pętli zwrotnej, musi również wielokrotnie skanować niektóre z tych większych obiektów.

Patrząc wstecz na źródło, zastanawiałem się, czy istnieje jakikolwiek inny predykat, który można by przekazać do widoku, który mógłby wymusić kształt planu, ale naprawdę nie sądzę, aby było coś, co mogłoby mieć wpływ.

Czy zadziała inny widok?

Moglibyśmy jednak wypróbować zupełnie inny pogląd. Szukałem innych widoków, które zawierały odniesienia do obu sys.sysrowsets i ALUCOUNT , a na liście pojawia się wiele, ale tylko dwa są obiecujące:sys.internal_partitions i sys.system_internals_partitions .

sys.internal_partitions

Próbowałem sys.internal_partitions po pierwsze:

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;

Ale plan nie był dużo lepszy (kliknij, aby powiększyć):

Plan dla sys.internal_partitions

Istnieją tylko dwa skany dla sys.sysrowsets tym razem, ale skany i tak są nieistotne, ponieważ zapytanie nie zbliża się do utworzenia wierszy, które nas interesują. Widzimy tylko wiersze dla obiektów związanych z magazynem kolumn (jak stwierdza dokumentacja).

sys.system_internals_partitions

Spróbujmy sys.system_internals_partitions . Nie przejmuję się tym, ponieważ nie jest to obsługiwane (patrz ostrzeżenie tutaj), ale wytrzymaj ze mną przez chwilę:

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;

W bazie danych z indeksami magazynu kolumn znajduje się skan pod kątem sys.sysschobjs , ale teraz tylko jeden skanuj z sys.sysrowsets (kliknij, aby powiększyć):

Plan dla sys.system_internals_partitions z obecnymi indeksami magazynu kolumn

Jeśli uruchomimy to samo zapytanie w bazie danych bez indeksów magazynu kolumn, plan jest jeszcze prostszy, z wyszukiwaniem w sys.sysschobjs (kliknij, aby powiększyć):

Plan dla sys.system_internals_partitions, bez indeksów magazynu kolumn

Jednak to nie jest do końca czego szukamy, a przynajmniej nie do końca tego, czego szukał Jake, ponieważ zawiera również artefakty z indeksów magazynu kolumn. Jeśli dodamy te filtry, rzeczywisty wynik będzie teraz pasował do naszego wcześniejszego, znacznie droższego zapytania:

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;

Jako bonus, skanowanie przeciwko sys.sysschobjs stał się wyszukiwaniem nawet w bazie danych z obiektami magazynu kolumn. Większość z nas nie zauważy tej różnicy, ale jeśli jesteś w scenariuszu takim jak Jake, możesz (kliknij, aby powiększyć):

Prostszy plan dla sys.system_internals_partitions, z dodatkowymi filtrami

sys.system_internals_partitions udostępnia inny zestaw kolumn niż sys.partitions (niektóre są zupełnie inne, inne mają nowe nazwy), więc jeśli zużywasz dane wyjściowe, będziesz musiał się do nich dostosować. Będziesz także chciał sprawdzić, czy zwraca wszystkie potrzebne informacje w indeksach magazynu wierszy, zoptymalizowanych pod kątem pamięci i magazynu kolumn, i nie zapomnij o tych nieznośnych stosach. I na koniec przygotuj się na pominięcie s w internals wiele, wiele razy.

Wniosek

Jak wspomniałem powyżej, ten widok systemowy nie jest oficjalnie obsługiwany, więc jego funkcjonalność może ulec zmianie w dowolnym momencie; można go również przenieść w ramach dedykowanego połączenia administratora (DAC) lub całkowicie usunąć z produktu. Możesz użyć tego podejścia, jeśli sys.partitions nie działa dobrze, ale upewnij się, że masz plan tworzenia kopii zapasowych. I upewnij się, że jest to udokumentowane jako coś, co testujesz regresją, kiedy zaczynasz testować przyszłe wersje SQL Server lub po aktualizacji, na wszelki wypadek.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Wstaw DML ze zmienną powiązania:UŻYWAJĄC klauzuli wykonania instrukcji natychmiastowej

  2. Podziel struny we właściwy sposób – lub następny najlepszy sposób

  3. Relacyjne vs nierelacyjne bazy danych – część 3

  4. T-SQL a SQL

  5. Ustawianie atrybutów połączenia ODBC bez konieczności pisania kodu