Mam dość dużą tabelę, w której jedną z kolumn są dane XML, a średni rozmiar wpisu XML wynosi ~ 15 kilobajtów. Wszystkie pozostałe kolumny są regularnymi liczbami całkowitymi, bigintami, identyfikatorami GUID itp. Aby mieć konkretne liczby, powiedzmy, że tabela ma milion wierszy i ma rozmiar ~ 15 GB.
Zauważyłem, że ta tabela naprawdę wolno wybiera dane, jeśli chcę zaznaczyć wszystkie kolumny. Kiedy robię
SELECT TOP 1000 * FROM TABLE
odczyt danych z dysku zajmuje około 20-25 sekund - mimo że nie narzucam żadnego uporządkowania wyniku. Uruchamiam zapytanie z zimną pamięcią podręczną (tj. Po DBCC DROPCLEANBUFFERS
). Oto wyniki statystyk IO:
Liczba skanów 1, logiczne odczyty 364, fizyczne odczyty 24, odczyty z wyprzedzeniem 7191, lob logiczne odczyty 7924, lob fizyczne odczyty 1690, lob odczyty z wyprzedzeniem 3968.
Przechwytuje ~ 15 MB danych. Plan wykonania pokazuje skanowanie indeksu klastrowanego, jak się spodziewałam.
Na dysku nie ma żadnych operacji wejścia / wyjścia poza moimi zapytaniami; Sprawdziłem również, czy fragmentacja indeksu klastrowego jest bliska 0%. Jest to dysk SATA klasy konsumenckiej, ale nadal sądzę, że SQL Server byłby w stanie przeskanować tabelę szybciej niż ~ 100-150 MB / min.
Obecność pola XML powoduje, że większość danych tabeli znajduje się na stronach LOB_DATA (w rzeczywistości ~ 90% stron tabeli to LOB_DATA).
Wydaje mi się, że moje pytanie brzmi - czy mam rację sądząc, że strony LOB_DATA mogą powodować powolne skanowanie nie tylko ze względu na ich rozmiar, ale także dlatego, że SQL Server nie może skutecznie skanować indeksu klastrowego, gdy w tabeli jest dużo stron LOB_DATA?
Jeszcze szerzej - czy uważa się za uzasadnione taką strukturę tabeli / wzorzec danych? Zalecenia dotyczące korzystania z Filestream zwykle zawierają znacznie większe pola, więc tak naprawdę nie chcę iść tą drogą. Tak naprawdę nie znalazłem żadnych dobrych informacji na temat tego konkretnego scenariusza.
Zastanawiałem się nad kompresją XML, ale trzeba to zrobić na kliencie lub za pomocą SQLCLR i wymagałoby to sporo pracy do wdrożenia w systemie.
Próbowałem kompresji, a ponieważ pliki XML są bardzo redundantne, mogę (w aplikacji ac #) kompresować XML z 20 KB do ~ 2,5 KB i przechowywać go w kolumnie VARBINARY, co zapobiega wykorzystaniu stron danych LOB. To przyspiesza WYBIERANIE 20 razy w moich testach.
źródło
SELECT *
nie stanowi problemu, jeśli potrzebujesz danych XML. To problem tylko wtedy, gdy nie chcesz danych XML. W takim przypadku dlaczego spowalniasz zapytanie, aby odzyskać dane, których nie używasz? Zapytałem o aktualizacje XML, zastanawiając się, czy fragmentacja na stronach LOB nie została dokładnie zgłoszona. Dlatego w mojej odpowiedzi zapytałem, jak dokładnie ustaliłeś, że indeks klastrowany nie został podzielony? Czy możesz podać polecenie, które uruchomiłeś? I czy wykonałeś pełne ODBUDOWANIE indeksu klastrowanego? (ciąg dalszy)Odpowiedzi:
Samo umieszczenie kolumny XML w tabeli nie ma tego efektu. Obecność danych XML powoduje , że pod pewnymi warunkami część danych wiersza jest przechowywana poza wierszem na stronach LOB_DATA. I choć jeden (a może kilka ;-) może argumentować, że
XML
kolumna oznacza, że rzeczywiście będą dane XML, nie ma gwarancji, że dane XML będą musiały być przechowywane poza wierszem: chyba że wiersz jest już prawie wypełniony poza tym, że są danymi XML, małe dokumenty (do 8000 bajtów) mogą zmieścić się w rzędzie i nigdy nie przechodzić na stronę LOB_DATA.Skanowanie odnosi się do spojrzenia na wszystkie rzędy. Oczywiście, gdy czytana jest strona danych, wszystkie wiersze , nawet jeśli wybrano podzbiór kolumn. Różnica w stosunku do danych LOB polega na tym, że jeśli nie wybierzesz tej kolumny, dane poza wierszem nie zostaną odczytane. Dlatego niesprawiedliwe jest wyciąganie wniosków na temat tego, jak skutecznie SQL Server może skanować ten Indeks klastrowany, ponieważ nie dokładnie go przetestowałeś (lub przetestowałeś jego połowę). Wybrałeś wszystkie kolumny, w tym kolumnę XML, i jak wspomniałeś, tam właśnie znajduje się większość danych.
Wiemy już więc, że
SELECT TOP 1000 *
test nie polegał jedynie na odczytaniu serii 8 000 stron danych, wszystkie w jednym rzędzie, ale przeskakiwaniu do innych lokalizacji w każdym rzędzie . Dokładna struktura tych danych LOB może się różnić w zależności od ich wielkości. Na podstawie badań pokazanych tutaj ( Jaki jest rozmiar wskaźnika LOB dla typów (MAX), takich jak Varchar, Varbinary, Etc? ), Istnieją dwa rodzaje przydziałów LOB poza wierszem:Jeden z tych dwóch sytuacji występuje za każdym razem pobierać dane LOB, które jest ponad 8000 bajtów lub po prostu nie pasował w wierszu. Zamieściłem skrypt testowy na PasteBin.com ( skrypt T-SQL do testowania przydziałów i odczytów LOB ), który pokazuje 3 typy przydziałów LOB (w zależności od wielkości danych), a także wpływ każdego z nich na logiczne i odczyty fizyczne. W twoim przypadku, jeśli dane XML naprawdę mają mniej niż 42 000 bajtów na wiersz, to żaden z nich (lub bardzo mało) nie powinien mieć najmniej wydajnej struktury TEXT_TREE.
Jeśli chcesz przetestować, jak szybko SQL Server może skanować ten Indeks klastrowany, wykonaj,
SELECT TOP 1000
ale określ jedną lub więcej kolumn nie zawierających tej kolumny XML. Jak to wpływa na twoje wyniki? Powinno być nieco szybsze.Biorąc pod uwagę, że mamy niepełny opis faktycznej struktury tabeli i wzorca danych, każda odpowiedź może nie być optymalna w zależności od brakujących szczegółów. Mając to na uwadze, powiedziałbym, że nie ma nic oczywiście nierozsądnego w strukturze tabeli lub wzorcu danych.
Dzięki temu zaznaczanie wszystkich kolumn, a nawet tylko danych XML (teraz w
VARBINARY
) jest szybsze, ale w rzeczywistości szkodzi zapytaniom, które nie wybierają danych „XML”. Zakładając, że masz około 50 bajtów w innych kolumnach i maszFILLFACTOR
100, a następnie:Bez kompresji: 15 000
XML
danych powinno wymagać 2 stron LOB_DATA, co wymaga 2 wskaźników dla Inline Root. Pierwszy wskaźnik ma 24 bajty, a drugi 12, w sumie 36 bajtów przechowywanych w wierszu dla danych XML. Całkowity rozmiar wiersza wynosi 86 bajtów i można zmieścić około 93 tych wierszy na stronie danych o wielkości 8060 bajtów. Dlatego 1 milion wierszy wymaga 10 753 stron danych.Kompresja niestandardowa: 2,5 tys.
VARBINARY
Danych zmieści się w rzędzie. Całkowity rozmiar wiersza wynosi 2610 (2,5 * 1024 = 2560) bajtów i można zmieścić tylko 3 z tych wierszy na stronie danych o długości 8060 bajtów. Dlatego 1 milion wierszy wymaga 333 334 stron danych.Ergo, wdrożenie niestandardowej kompresji powoduje 30-krotny wzrost liczby stron danych dla indeksu klastrowanego. Znaczenie wszystkich zapytań z użyciem skanowania indeksu klastrowego mają teraz około 322,500 więcej stron danych do odczytania. Proszę zapoznać się ze szczegółową sekcją poniżej, aby uzyskać dodatkowe konsekwencje tego typu kompresji.
Przestrzegałbym przed robieniem jakichkolwiek refaktoryzacji opartych na wydajności
SELECT TOP 1000 *
. Prawdopodobnie nie będzie to zapytanie, które aplikacja wygeneruje, i nie powinno być wykorzystywane jako jedyna podstawa do potencjalnie niepotrzebnych optymalizacji.Aby uzyskać bardziej szczegółowe informacje i więcej testów do wypróbowania, zobacz sekcję poniżej.
Na to pytanie nie można udzielić ostatecznej odpowiedzi, ale możemy przynajmniej poczynić pewne postępy i zasugerować dodatkowe badania, które pomogą nam przybliżyć się do dokładnego rozwiązania problemu (najlepiej na podstawie dowodów).
Co wiemy:
XML
kolumnę oraz kilka innych kolumn typów:INT
,BIGINT
,UNIQUEIDENTIFIER
, „itp”XML
kolumna „rozmiar” wynosi średnio około 15 tysDBCC DROPCLEANBUFFERS
następująca kwerenda zajmuje 20–25 sekund:SELECT TOP 1000 * FROM TABLE
Co naszym zdaniem wiemy:
Kompresja XML może pomóc. Jak dokładnie zrobiłbyś kompresję w .NET? Poprzez GZipStream lub DeflateStream klas? To nie jest opcja o zerowym koszcie. Z pewnością skompresuje niektóre dane o duży procent, ale będzie również wymagało więcej procesora, ponieważ będziesz potrzebował dodatkowego procesu do kompresji / dekompresji danych za każdym razem. Ten plan całkowicie wyeliminowałby również Twoją zdolność do:
.nodes
,.value
,.query
, i.modify
funkcje XML.indeksować dane XML.
Należy pamiętać (ponieważ wspomniano, że XML jest „bardzo redundantny”), że
XML
typ danych jest już zoptymalizowany, ponieważ przechowuje nazwy elementów i atrybutów w słowniku, przypisując identyfikator indeksu liczb całkowitych do każdego elementu, a następnie używając tego identyfikatora liczb całkowitych w całym dokumencie (stąd nie powtarza pełnej nazwy dla każdego użycia, ani nie powtarza go ponownie jako tag zamykający dla elementów). Rzeczywiste dane usunęły również obce białe miejsca. Dlatego wyodrębnione dokumenty XML nie zachowują swojej oryginalnej struktury i dlaczego puste elementy wyodrębniają się tak,<element />
jakby weszły jako<element></element>
. Tak więc wszelkie korzyści z kompresji za pomocą GZip (lub cokolwiek innego) można znaleźć tylko poprzez kompresję wartości elementu i / lub wartości atrybutu, co jest znacznie mniejszą powierzchnią, którą można poprawić, niż większość by się spodziewała, i najprawdopodobniej nie jest warta utraty możliwości, jak wspomniano bezpośrednio powyżej.Należy również pamiętać, że kompresja danych XML i przechowywanie
VARBINARY(MAX)
wyniku nie wyeliminuje dostępu do LOB, po prostu go zmniejszy. W zależności od wielkości reszty danych w wierszu skompresowana wartość może zmieścić się w wierszu lub może nadal wymagać stron LOB.Ta informacja, choć pomocna, nie jest prawie wystarczająca. Istnieje wiele czynników wpływających na wydajność zapytań, dlatego potrzebujemy znacznie bardziej szczegółowego obrazu tego, co się dzieje.
Czego nie wiemy, ale musimy:
SELECT *
materii? Czy jest to wzorzec używany w kodzie? Jeśli tak, dlaczego?SELECT TOP 1000 XmlColumn FROM TABLE;
:?Ile z 20–25 sekund potrzebnych na zwrócenie tych 1000 wierszy jest związanych z czynnikami sieciowymi (przesyłanie danych przez sieć), a ile z czynnikami klienta (renderowanie około 15 MB plus reszta nie- Dane XML do siatki w SSMS, a może zapisywanie na dysk)?
Rozróżnienie tych dwóch aspektów operacji może czasem być wykonane po prostu przez nie zwracanie danych. Teraz można pomyśleć o wyborze tabeli tymczasowej lub zmiennej tabeli, ale wprowadziłoby to tylko kilka nowych zmiennych (tj. Dysk I / O dla
tempdb
, zapisy dziennika transakcji, możliwy automatyczny wzrost danych tempdb i / lub pliku dziennika, potrzeba spacja w puli buforów itp.). Wszystkie te nowe czynniki mogą faktycznie wydłużyć czas zapytania. Zamiast tego zazwyczaj przechowuję kolumny w zmiennych (odpowiedniego typu danych; nieSQL_VARIANT
), które są zastępowane każdym nowym wierszem (tjSELECT @Column1 = tab.Column1,...
.).JEDNAK , jak wskazał @PaulWhite w tym pytaniu i odpowiedziach DBA.StackExchange, Logical czyta inaczej przy dostępie do tych samych danych LOB , z dodatkowymi badaniami moich własnych opublikowanych na PasteBin ( skrypt T-SQL do testowania różnych scenariuszy dla odczytów LOB ) , LOB nie są dostępne między konsekwentnie
SELECT
,SELECT INTO
,SELECT @XmlVariable = XmlColumn
,SELECT @XmlVariable = XmlColumn.query(N'/')
, iSELECT @NVarCharVariable = CONVERT(NVARCHAR(MAX), XmlColumn)
. Nasze opcje są tutaj nieco bardziej ograniczone, ale oto, co można zrobić:Alternatywnie, można wykonać zapytanie poprzez SQLCMD.EXE i przekierować iść donikąd poprzez:
-o NUL:
.Jaki jest rzeczywisty rozmiar danych dla zwracanych
XML
kolumn ? Średni rozmiar tej kolumny w całej tabeli nie ma tak naprawdę znaczenia, jeśli wiersze „TOP 1000” zawierają nieproporcjonalnie dużą część wszystkich danych. Jeśli chcesz wiedzieć o TOP 1000 wierszach, spójrz na te wiersze. Uruchom następujące czynności:XML
CREATE TABLE
oświadczenie, w tym wszystkie indeksy.Jakie są dokładne wyniki następującego zapytania:
AKTUALIZACJA
Przyszło mi do głowy, że powinienem spróbować odtworzyć ten scenariusz, aby sprawdzić, czy mam podobne zachowanie. Tak więc utworzyłem tabelę z kilkoma kolumnami (podobnymi do niejasnego opisu w pytaniu), a następnie zapełniłem ją 1 milionem wierszy, a kolumna XML zawiera około 15k danych na wiersz (patrz kod poniżej).
Zauważyłem, że wykonanie
SELECT TOP 1000 * FROM TABLE
zadania za pierwszym razem zakończyło się w 8 sekund, a potem za każdym razem 2–4 sekundy (tak, wykonywanieDBCC DROPCLEANBUFFERS
przed każdym uruchomieniemSELECT *
zapytania). A mój kilkuletni laptop nie jest szybki: SQL Server 2012 SP2 Developer Edition, 64-bitowy, 6 GB RAM, podwójny 2,5 GHz Core i5 i dysk SATA 5400 RPM. Używam również SSMS 2014, SQL Server Express 2014, Chrome i kilku innych rzeczy.W oparciu o czas odpowiedzi mojego systemu powtórzę, że potrzebujemy więcej informacji (tj. Dane szczegółowe dotyczące tabeli i danych, wyniki sugerowanych testów itp.), Aby pomóc zawęzić przyczynę 20–25 sekundowego czasu odpowiedzi które widzisz.
A ponieważ chcemy odliczyć czas potrzebny na odczytanie stron spoza LOB, uruchomiłem następujące zapytanie, aby zaznaczyć wszystkie oprócz kolumny XML (jeden z testów, które zasugerowałem powyżej). Powraca to w ciągu 1,5 sekundy dość konsekwentnie.
Wniosek (na razie)
Na podstawie mojej próby odtworzenia twojego scenariusza nie sądzę, abyśmy mogli wskazać dysk SATA lub niesekwencyjne operacje we / wy jako główną przyczynę 20–25 sekund, szczególnie dlatego, że wciąż nie wiem, jak szybko zwraca zapytanie, jeśli nie zawiera kolumny XML. I nie byłem w stanie odtworzyć dużej liczby odczytów logicznych (nie LOB), które wyświetlasz, ale mam wrażenie, że muszę dodać więcej danych do każdego wiersza w świetle tego i oświadczenia:
Moja tabela ma 1 milion wierszy, z których każdy zawiera nieco ponad 15 000 danych XML i
sys.dm_db_index_physical_stats
pokazuje, że istnieją 2 miliony stron LOB_DATA. Pozostałe 10% to wtedy 222 tys. Stron danych IN_ROW, ale mam ich tylko 11,630. Po raz kolejny potrzebujemy więcej informacji dotyczących faktycznego schematu tabeli i rzeczywistych danych.źródło
Tak, odczyt danych LOB niezapisanych w wierszu prowadzi do losowego We / Wy zamiast sekwencyjnego We / Wy. Miarą wydajności dysku, którą należy tutaj zastosować, aby zrozumieć, dlaczego jest on szybki lub wolny, jest IOPS odczytu losowego.
Dane LOB są przechowywane w strukturze drzewa, gdzie strona danych w indeksie klastrowym wskazuje stronę danych LOB ze strukturą główną LOB, która z kolei wskazuje na rzeczywiste dane LOB. Podczas przechodzenia przez węzły główne w indeksie klastrowym SQL Server może uzyskać dane w wierszu tylko poprzez sekwencyjne odczyty. Aby uzyskać dane LOB, SQL Server musi iść gdzieś indziej na dysku.
Wydaje mi się, że gdybyś zmienił dysk SSD, nie ucierpiałby na tym tyle, ponieważ losowe operacje IOPS dla dysku SSD są znacznie wyższe niż w przypadku wirującego dysku.
Tak, może być. Zależy od tego, co ten stół dla Ciebie robi.
Zwykle problemy z wydajnością XML w SQL Server występują, gdy chcesz użyć T-SQL do zapytania do XML, a tym bardziej, gdy chcesz użyć wartości z XML w predykacie w klauzuli where lub złączeniu. W takim przypadku możesz rzucić okiem na promocję właściwości lub selektywne indeksy XML lub przeprojektowanie struktur tabel niszcząc XML do tabel.
Zrobiłem to raz w produkcie nieco ponad 10 lat temu i od tego czasu żałuję. Naprawdę tęskniłem za niemożnością pracy z danymi przy użyciu T-SQL, więc nie poleciłbym tego nikomu, jeśli można tego uniknąć.
źródło