LOB_DATA, wolne skanowanie tabeli i niektóre pytania we / wy

19

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.

Alexander Shelemin
źródło
Alex: nie jestem pewien, czy widziałeś dyskusję związaną z moją odpowiedzią (link znajduje się w komentarzu pod moją odpowiedzią), ale udało mi się zbliżyć do odtworzenia twojego scenariusza. Wypełniłem tabelę pasującą do twojego opisu (tyle, ile miałem informacji) i otrzymałem statystyki we / wy, które są bardzo podobne. Tyle że „Fizyczne odczyty LOB” nigdy nie były nawet bliskie. Zastanawiałem się więc, czy zaktualizowałeś XML (ale nie inne kolumny) i / lub miałeś dużo fizycznej fragmentacji twoich plików danych. Nadal nie miałbym nic przeciwko uzyskaniu DDL tabeli i ustawienia automatycznego wzrostu dla każdego pliku danych, a czy zmniejszasz swoje pliki danych?
Solomon Rutzky
Przede wszystkim - wielkie dzięki za szczegółową odpowiedź, nie mogłem wtedy uczestniczyć w dyskusji z powodu braku czasu. Teraz, kiedy o tym wspomniałeś (nie pomyślałem o tym, gdy zadałem pytanie) - pole XML jest aktualizowane wiele razy po utworzeniu i jest małe. Podejrzewam więc, że początkowo jest przechowywany w rzędzie, a po niektórych aktualizacjach zostaje przeniesiony do struktury strony LOB, a następnie otrzymuje kilka innych aktualizacji.
Alexander Shelemin
(Ciąg dalszy) Sprawdziłem fizyczną fragmentację plików przed zadaniem pytania, a wbudowane narzędzie Windows uznało, że jest OK, więc nie zagłębiałem się w to. Automatyczny wzrost jest domyślny, sądzę, że o 1 MB, a pliki danych nie zostały zmniejszone.
Alexander Shelemin
Wybierz najlepsze 1000 * ma znaczenie w moim konkretnym przypadku. Z pewnością rozumiem, że jest to uważane za złą praktykę, jednak niektóre decyzje dotyczące projektowania aplikacji są naprawdę trudne do zmiany po długim okresie ich obowiązywania. Select * jest zasadniczo używany jako strategia replikacji między bazami danych między różnymi komponentami w naszej aplikacji. Są w tym plusy, na przykład możemy wykonywać dowolne manipulacje danymi / schematem w locie, co byłoby trudne przy wbudowanych technikach replikacji, ale wiąże się to z problemami.
Alexander Shelemin
Alex, 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)
Solomon Rutzky

Odpowiedzi:

11

Obecność pola XML powoduje, że większość danych tabeli znajduje się na stronach LOB_DATA (w rzeczywistości ~ 90% stron tabeli to LOB_DATA).

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 XMLkolumna 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.

czy mam rację, myślą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 wiele 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:

  1. Inline Root - dla danych od 8001 do 40 000 (naprawdę 42 000) bajtów, jeśli pozwala na to miejsce, będzie od 1 do 5 wskaźników (24 - 72 bajtów) W RZĄDZIE, które wskazują bezpośrednio na stronę (strony) LOB.
  2. DRZEWO TEKSTOWE - w przypadku danych przekraczających 42 000 bajtów lub jeśli od 1 do 5 wskaźników nie mieści się w rzędzie, wówczas na stronie początkowej listy wskaźników na stronach LOB będzie tylko 24-bajtowy wskaźnik (tzn. „ strona_tekstowa ”).

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 1000ale określ jedną lub więcej kolumn nie zawierających tej kolumny XML. Jak to wpływa na twoje wyniki? Powinno być nieco szybsze.

czy uzasadnione jest posiadanie takiej struktury tabeli / wzorca danych?

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.

Mogę (w aplikacji ac #) kompresować XML z 20KB do ~ 2,5KB i przechowywać go w kolumnie VARBINARY, co zapobiega użyciu stron danych LOB. To przyspiesza WYBIERANIE 20 razy w moich testach.

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 masz FILLFACTOR100, a następnie:

  • Bez kompresji: 15 000 XMLdanych 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. VARBINARYDanych 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:

  1. Tabela ma około 1 miliona wierszy
  2. Rozmiar stołu to około 15 GB
  3. Tabela zawiera jedną XMLkolumnę oraz kilka innych kolumn typów: INT, BIGINT, UNIQUEIDENTIFIER, „itp”
  4. XMLkolumna „rozmiar” wynosi średnio około 15 tys
  5. Po uruchomieniu DBCC DROPCLEANBUFFERSnastępująca kwerenda zajmuje 20–25 sekund:SELECT TOP 1000 * FROM TABLE
  6. Indeks klastrowany jest skanowany
  7. Fragmentacja indeksu klastrowego jest bliska 0%

Co naszym zdaniem wiemy:

  1. Żadna inna aktywność dysku poza tymi zapytaniami. Jesteś pewny? Nawet jeśli nie ma innych zapytań użytkowników, czy mają miejsce operacje w tle? Czy istnieją procesy zewnętrzne względem programu SQL Server działające na tym samym komputerze, które mogą zajmować część operacji we / wy? Może nie być, ale nie jest jasne na podstawie wyłącznie dostarczonych informacji.
  2. Zwracane jest 15 MB danych XML. Na czym opiera się ten numer? Oszacowanie wyprowadzone z 1000 wierszy razy średnio 15k danych XML na wiersz? Lub programowa agregacja tego, co otrzymano dla tego zapytania? Jeśli jest to tylko oszacowanie, nie polegałbym na nim, ponieważ dystrybucja danych XML może nie być nawet w sposób sugerowany przez zwykłą średnią.
  3. 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:

    • Zapytanie o dane XML przez .nodes, .value, .query, i .modifyfunkcje XML.
    • indeksować dane XML.

      Należy pamiętać (ponieważ wspomniano, że XML jest „bardzo redundantny”), że XMLtyp 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:

  1. Dlaczego występowanie SELECT *materii? Czy jest to wzorzec używany w kodzie? Jeśli tak, dlaczego?
  2. Jaka jest wydajność zaznaczania tylko kolumny XML? Jakie są statystyki i czas, jeśli wykonasz tylko SELECT TOP 1000 XmlColumn FROM TABLE;:?
  3. 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; nie SQL_VARIANT), które są zastępowane każdym nowym wierszem (tj SELECT @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'/'), i SELECT @NVarCharVariable = CONVERT(NVARCHAR(MAX), XmlColumn). Nasze opcje są tutaj nieco bardziej ograniczone, ale oto, co można zrobić:

    1. Wyeliminuj problemy z siecią, wykonując zapytanie na serwerze z programem SQL Server, w SSMS lub SQLCMD.EXE.
    2. Wyklucz problemy klienta w SSMS, przechodząc do Opcje zapytania -> Wyniki -> Siatka i zaznaczając opcję „Odrzuć wyniki po wykonaniu”. Należy pamiętać, że ta opcja zapobiegnie WSZYSTKIM wyjściom, w tym wiadomościom, ale nadal może być użyteczna, aby wykluczyć czas potrzebny SSMS na przydzielenie pamięci dla każdego wiersza, a następnie narysowanie jej w siatce.
      Alternatywnie, można wykonać zapytanie poprzez SQLCMD.EXE i przekierować iść donikąd poprzez: -o NUL:.
  4. Czy z tym zapytaniem jest powiązany typ oczekiwania? Jeśli tak, co to za typ oczekiwania?
  5. Jaki jest rzeczywisty rozmiar danych dla zwracanychXML 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

    SELECT TOP 1000 tab.*,
           SUM(DATALENGTH(tab.XmlColumn)) / 1024.0 AS [TotalXmlKBytes],
           AVG(DATALENGTH(tab.XmlColumn)) / 1024.0 AS [AverageXmlKBytes]
           STDEV(DATALENGTH(tab.XmlColumn)) / 1024.0 AS [StandardDeviationForXmlKBytes]
    FROM   SchemaName.TableName tab;
  6. Dokładny schemat tabeli. Proszę podać pełne CREATE TABLE oświadczenie, w tym wszystkie indeksy.
  7. Plan zapytań? Czy to coś, co możesz opublikować? Te informacje prawdopodobnie nic nie zmienią, ale lepiej wiedzieć, że nie, niż zgadywać, że nie będzie i nie będzie w błędzie ;-)
  8. Czy w pliku danych występuje fragmentacja fizyczna / zewnętrzna? Chociaż może to nie być duży czynnik, ponieważ używasz „SATA klasy konsumenckiej”, a nie SSD, a nawet Super-drogich SATA, efekt mniej optymalnie uporządkowanych sektorów będzie bardziej zauważalny, szczególnie ze względu na liczbę tych sektorów które należy odczytać zwiększa się.
  9. Jakie są dokładne wyniki następującego zapytania:

    SELECT * FROM sys.dm_db_index_physical_stats(DB_ID(),
                              OBJECT_ID(N'dbo.SchemaName.TableName'), 1, 0, N'LIMITED');

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 TABLEzadania za pierwszym razem zakończyło się w 8 sekund, a potem za każdym razem 2–4 sekundy (tak, wykonywanie DBCC DROPCLEANBUFFERSprzed każdym uruchomieniem SELECT *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.

SET ANSI_NULLS, NOCOUNT ON;
GO

IF (OBJECT_ID(N'dbo.XmlReadTest') IS NOT NULL)
BEGIN
    PRINT N'Dropping table...';
    DROP TABLE dbo.XmlReadTest;
END;

PRINT N'Creating table...';
CREATE TABLE dbo.XmlReadTest 
(
    ID INT NOT NULL IDENTITY(1, 1),
    Col2 BIGINT,
    Col3 UNIQUEIDENTIFIER,
    Col4 DATETIME,
    Col5 XML,
    CONSTRAINT [PK_XmlReadTest] PRIMARY KEY CLUSTERED ([ID])
);
GO

DECLARE @MaxSets INT = 1000,
        @CurrentSet INT = 1;

WHILE (@CurrentSet <= @MaxSets)
BEGIN
    RAISERROR(N'Populating data (1000 sets of 1000 rows); Set # %d ...',
              10, 1, @CurrentSet) WITH NOWAIT;
    INSERT INTO dbo.XmlReadTest (Col2, Col3, Col4, Col5)
        SELECT  TOP 1000
                CONVERT(BIGINT, CRYPT_GEN_RANDOM(8)),
                NEWID(),
                GETDATE(),
                N'<test>'
                  + REPLICATE(CONVERT(NVARCHAR(MAX), CRYPT_GEN_RANDOM(1), 2), 3750)
                  + N'</test>'
        FROM        [master].[sys].all_columns sac1;

    IF ((@CurrentSet % 100) = 0)
    BEGIN
        RAISERROR(N'Executing CHECKPOINT ...', 10, 1) WITH NOWAIT;
        CHECKPOINT;
    END;

    SET @CurrentSet += 1;
END;

--

SELECT COUNT(*) FROM dbo.XmlReadTest; -- Verify that we have 1 million rows

-- O.P. states that the "clustered index fragmentation is close to 0%"
ALTER INDEX [PK_XmlReadTest] ON dbo.XmlReadTest REBUILD WITH (FILLFACTOR = 90);
CHECKPOINT;

--

DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;

SET STATISTICS IO, TIME ON;
SELECT TOP 1000 * FROM dbo.XmlReadTest;
SET STATISTICS IO, TIME OFF;

/*
Scan count 1, logical reads 21,       physical reads 1,     read-ahead reads 4436,
              lob logical reads 5676, lob physical reads 1, lob read-ahead reads 3967.

 SQL Server Execution Times:
   CPU time = 171 ms,  elapsed time = 8329 ms.
*/

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.

DBCC DROPCLEANBUFFERS WITH NO_INFOMSGS;

SET STATISTICS IO, TIME ON;
SELECT TOP 1000 ID, Col2, Col3, Col4 FROM dbo.XmlReadTest;
SET STATISTICS IO, TIME OFF;

/*
Scan count 1, logical reads 21,    physical reads 1,     read-ahead reads 4436,
              lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 1666 ms.
*/

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:

~ 90% stron tabeli to LOB_DATA

Moja tabela ma 1 milion wierszy, z których każdy zawiera nieco ponad 15 000 danych XML i sys.dm_db_index_physical_statspokazuje, ż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.

Solomon Rutzky
źródło
Ta dyskusja została przeniesiona do czatu .
Paul White mówi GoFundMonica
10

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 klastrowanego

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.

czy uzasadnione jest posiadanie takiej struktury tabeli / wzorca danych?

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.

Próbowałem kompresji

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ąć.

Mikael Eriksson
źródło
Wielkie dzięki za odpowiedź. Jeśli chodzi o kompresję: nie jestem pewien, czy tak rygorystyczne anty zalecenie jest uzasadnione, ponieważ potrzeba faktycznego zapytania tych danych z T-SQL oczywiście zależy od charakteru przechowywanych danych. W moim przypadku zdecydowałem się na kompresję.
Alexander Shelemin