512 bajtów nie jest używanych ze strony danych SQL Server o wielkości 8 KB

13

Utworzyłem następującą tabelę:

CREATE TABLE dbo.TestStructure
(
    id INT NOT NULL,
    filler1 CHAR(36) NOT NULL,
    filler2 CHAR(216) NOT NULL
);

a następnie utworzył indeks klastrowany:

CREATE CLUSTERED INDEX idx_cl_id 
ON dbo.TestStructure(id);

Następnie zapełniłem go 30 wierszami, każdy rozmiar to 256 bajtów (na podstawie deklaracji tabeli):

DECLARE @i AS int = 0;

WHILE @i < 30
BEGIN
    SET @i = @i + 1;

    INSERT INTO dbo.TestStructure (id, filler1, filler2)
    VALUES (@i, 'a', 'b');
END;

Teraz na podstawie informacji, które przeczytałem w książce „Zestaw szkoleniowy (egzamin 70-461): Sprawdzanie Microsoft SQL Server 2012 (Itzik Ben-Gan)”:

SQL Server wewnętrznie organizuje dane w pliku danych na stronach. Strona jest jednostką o wielkości 8 KB i należy do jednego obiektu; na przykład do tabeli lub indeksu. Strona to najmniejsza jednostka do czytania i pisania. Strony są dalej uporządkowane w zakresie. Zakres składa się z ośmiu kolejnych stron. Strony z zakresu mogą należeć do jednego obiektu lub wielu obiektów. Jeśli strony należą do wielu obiektów, wówczas zasięg nazywany jest zasięgiem mieszanym; jeśli strony należą do jednego obiektu, wówczas zasięg nazywany jest zasięgiem jednolitym. SQL Server przechowuje pierwsze osiem stron obiektu w różnych zakresach. Gdy obiekt przekracza osiem stron, SQL Server przydziela dodatkowe jednolite zakresy dla tego obiektu. Dzięki tej organizacji małe obiekty marnują mniej miejsca, a duże obiekty są mniej rozdrobnione.

Więc tutaj mam pierwszą stronę o rozmiarze 8KB o mieszanym rozmiarze, wypełnioną 7680 bajtami (wstawiłem 30 razy 256-bajtowy wiersz wielkości, więc 30 * 256 = 7680), aby sprawdzić rozmiar, mam uruchomiony sprawdzanie rozmiaru proc - zwraca następujący wynik

index_type_desc: CLUSTERED INDEX
index_depth: 1
index_level: 0 
page_count: 1 
record_count: 30 
avg_page_space_used_in_percent: 98.1961947121324
name : TestStructure        
rows : 30   
reserved :  16 KB
data : 8 KB 
index_size : 8 KB       
unused :    0 KB

Tak więc 16 KB jest zarezerwowanych dla tabeli, pierwsza strona 8 KB dotyczy strony root IAM, druga strona do przechowywania danych liści, która ma 8 KB z zajętością ~ 7,5 KB, teraz kiedy wstawię nowy wiersz z 256 bajtami:

INSERT INTO dbo.TestStructure (id, filler1, filler2)
VALUES (1, 'a', 'b');

nie jest przechowywany na tej samej stronie, chociaż ma przestrzeń 256 bajtów (7680 b + 256 = 7936, która wciąż jest mniejsza niż 8 KB), tworzona jest nowa strona danych, ale ten nowy wiersz może być dopasowany do tej samej starej strony , dlaczego SQL Server tworzy nową stronę, skoro może zaoszczędzić miejsce i czas wyszukiwania, kupując wstawianie jej na istniejącą stronę?

Uwaga: to samo dzieje się w indeksie sterty.

Alphas Supremum
źródło

Odpowiedzi:

9

Twoje rzędy danych nie mają 256 bajtów. Każdy z nich jest bardziej jak 263 bajtów. Wiersz danych o typach danych o ściśle określonej długości ma dodatkowy narzut ze względu na strukturę wiersza danych w programie SQL Server. Spójrz na tę stronę i przeczytaj, jak składa się wiersz danych. http://aboutsqlserver.com/2013/10/15/sql-server-storage-engine-data-pages-and-data-rows/

Tak więc w twoim przykładzie masz wiersz danych, który ma 256 bajtów, dodajesz 2 bajty dla bitów statusu, 2 bajty dla liczby kolumn, 2 bajty dla długości danych i kolejny 1 lub mniej dla pustej mapy bitowej. To jest 263 * 30 = 7890 bajtów. Dodaj kolejne 263, a przekroczysz limit 8 KB strony, co wymusiłoby utworzenie innej strony.

dfundako
źródło
Podany przez Ciebie link pomógł mi lepiej wizualnie struktury strony. Szukałem czegoś podobnego, ale nie mogłem tego znaleźć, Thax
Alphas Supremum
11

Chociaż prawdą jest, że SQL Server używa stron danych o wielkości 8k (8192 bajtów) do przechowywania 1 lub więcej wierszy, każda strona danych ma narzut (96 bajtów), a każdy wiersz ma narzut (co najmniej 9 bajtów). 8192 bajtów to nie tylko dane.

Aby uzyskać bardziej szczegółowe badanie tego, jak to działa, proszę zobaczyć moją odpowiedź na następujące pytanie DBA.SE:

SUMA DATALENGTH niezgodnych z rozmiarem tabeli z sys.allocation_units

Korzystając z informacji w tej połączonej odpowiedzi, możemy uzyskać wyraźniejszy obraz rzeczywistego rozmiaru wiersza:

  1. Nagłówek wiersza = 4 bajty
  2. Liczba kolumn = 2 bajty
  3. NULL Bitmap = 1 bajt
  4. Informacje o wersji ** = 14 bajtów (opcjonalnie, patrz przypis)
  5. Łącznie narzut na wiersz (bez macierzy szczelin) = minimum 7 bajtów lub 21 bajtów, jeśli dostępne są informacje o wersji
  6. Całkowity rzeczywisty rozmiar wiersza = minimum 263 (256 danych + 7 narzutów) lub 277 bajtów (256 danych + 21 narzutów), jeśli dostępne są informacje o wersji
  7. Dodając do tablicy miejsc, całkowite miejsce zajmowane przez wiersz wynosi w rzeczywistości albo 265 bajtów (bez informacji o wersji), albo 279 bajtów (z informacjami o wersji).

Użycie DBCC PAGEpotwierdza moje obliczenia, pokazując: Record Size 263(dla tempdb) i Record Size 277(dla bazy danych, która jest ustawiona na ALLOW_SNAPSHOT_ISOLATION ON).

Teraz, z 30 rzędami, to jest:

  • BEZ informacji o wersji

    30 * 263 dałoby nam 7890 bajtów. Następnie dodaj 96 bajtów nagłówka strony dla 7986 użytych bajtów. Na koniec dodaj 60 bajtów (2 na wiersz) tablicy gniazd, aby uzyskać łącznie 8046 bajtów używanych na stronie i 146 pozostałych. Użycie DBCC PAGEpotwierdza moje obliczenia, pokazując:

    • m_slotCnt 30 (tj. liczba rzędów)
    • m_freeCnt 146 (tj. liczba bajtów pozostałych na stronie)
    • m_freeData 7986 (tzn. dane + nagłówek strony - 7890 + 96 - tablica szczelin nie jest uwzględniana w obliczeniach „używanych” bajtów)
  • Z informacjami o wersji

    30 * 277 bajtów, co daje w sumie 8310 bajtów. Ale 8310 to ponad 8192, a to nawet nie uwzględniało 96-bajtowego nagłówka strony ani 2-bajtowej tablicy gniazd wierszy (30 * 2 = 60 bajtów), co powinno dać nam tylko 8036 użytecznych bajtów dla wierszy.

    ALE co z 29 rzędami? To dałoby nam 8033 bajtów danych (29 * 277) + 96 bajtów dla nagłówka strony + 58 bajtów dla tablicy gniazd (29 * 2) równej 8187 bajtów. I to pozostawiłoby stronę z 5 bajtami (8192 - 8187; oczywiście bezużyteczne). Użycie DBCC PAGEpotwierdza moje obliczenia, pokazując:

    • m_slotCnt 29 (tj. liczba rzędów)
    • m_freeCnt 5 (tj. liczba bajtów pozostałych na stronie)
    • m_freeData 8129 (tzn. dane + nagłówek strony - 8033 + 96 - tablica szczelin nie jest uwzględniana w obliczeniach „używanych” bajtów)

Odnośnie stert

Sterty wypełniają strony danych nieco inaczej. Utrzymują bardzo przybliżone oszacowanie ilości miejsca pozostałego na stronie. Patrząc na wyjściu DBCC, spojrzeć na wierszu: PAGE HEADER: Allocation Status PFS (1:1). Zobaczysz VALUEpokazywanie czegoś wzdłuż linii 0x60 MIXED_EXT ALLOCATED 0_PCT_FULL(kiedy spojrzałem na stół klastrowany) lub 0x64 MIXED_EXT ALLOCATED 100_PCT_FULLpatrząc na stół ze stertami. Jest to oceniane dla każdej transakcji, więc wykonanie pojedynczych wstawek, takich jak przeprowadzany tutaj test, może pokazać różne wyniki między tabelami klastrowanymi i stertami. Wykonanie pojedynczej operacji DML dla wszystkich 30 wierszy wypełni jednak stertę zgodnie z oczekiwaniami.

Jednak żaden z tych szczegółów dotyczących Stert nie wpływa bezpośrednio na ten konkretny test, ponieważ obie wersje tabeli mieszczą 30 wierszy, a pozostało tylko 146 bajtów. To za mało miejsca na kolejny rząd, niezależnie od Gromady lub Sterty.

Należy pamiętać, że ten test jest raczej prosty. Obliczanie rzeczywistego rozmiaru wiersza może być bardzo skomplikowane w zależności od różnych czynników, takich jak SPARSE:, kompresja danych, dane LOB itp.


Aby wyświetlić szczegóły strony danych, użyj następującego zapytania:

DECLARE @PageID INT,
        @FileID INT,
        @DatabaseID SMALLINT = DB_ID();

SELECT  @FileID = alloc.[allocated_page_file_id],
        @PageID = alloc.[allocated_page_page_id]
FROM    sys.dm_db_database_page_allocations(@DatabaseID,
                            OBJECT_ID(N'dbo.TestStructure'), 1, NULL, 'DETAILED') alloc
WHERE   alloc.[previous_page_page_id] IS NULL -- first data page
AND     alloc.[page_type] = 1; -- DATA_PAGE

DBCC PAGE(@DatabaseID, @FileID, @PageID, 3) WITH TABLERESULTS;

** 14-bajtowa wartość „informacji o wersji” będzie dostępna, jeśli baza danych jest ustawiona na jeden ALLOW_SNAPSHOT_ISOLATION ONlub READ_COMMITTED_SNAPSHOT ON.

Solomon Rutzky
źródło
Dokładnie 8060 bajtów na stronę jest dostępnych dla danych użytkownika. Dane PO są wciąż poniżej tego.
Roger Wolf,
Nie ma informacji o wersji, w przeciwnym razie 30 wierszy zajmie 8310 bajtów. Reszta wydaje się poprawna.
Roger Wolf,
@RogerWolf Tak, jest tam „informacja o wersji”. Tak więc, 30 wierszy wymaga 8310 bajtów. Właśnie dlatego te 30 wierszy w rzeczywistości nie mieściło się na 1 stronie, ponieważ OP jest przekonany przez dowolny proces testowy, którego używa OP. Ale ten proces testowy jest zły. Tylko 29 wierszy mieści się na stronie. Potwierdziłem to (nawet używając SQL Server 2012).
Solomon Rutzky
próbowałeś uruchomić test na bazie danych, która nie obsługuje RCSI / tempdb? Udało mi się odtworzyć dokładne liczby podane przez OP.
Roger Wolf,
@RogerWolf DB, którego używam, nie obsługuje RCSI, ale jest ustawiony na ALLOW_SNAPSHOT_ISOLATION. Właśnie próbowałem tempdbi zobaczyłem, że nie ma „informacji o wersji”, dlatego pasuje 30 wierszy. Zaktualizuję, aby dodać nowe informacje.
Solomon Rutzky
3

Rzeczywista struktura strony danych jest dość złożona. Chociaż ogólnie stwierdza się, że na dane użytkownika dostępnych jest 8060 bajtów na stronę, istnieje pewne dodatkowe obciążenie, które nie jest liczone nigdzie, co powoduje takie zachowanie.

Być może zauważyłeś, że SQL Server faktycznie daje ci wskazówkę, że 31-szy wiersz nie zmieści się na stronie. Aby następny wiersz zmieścił się na tej samej stronie, avg_page_space_used_in_percentwartość powinna wynosić poniżej 100% - (100/31) = 96,774194, a w twoim przypadku jest to znacznie powyżej.

PS Wydaje mi się, że widziałem szczegółowe, aż do bajtowego wyjaśnienia struktury strony danych, w jednej z książek Kalena Delaneya „SQL Server Internals”, ale było to prawie 10 lat temu, więc przepraszam, że nie pamiętam więcej szczegółów. Poza tym struktura strony zmienia się z wersji na wersję.

Roger Wolf
źródło
1
Nie. Unikator jest dodawany tylko do duplikatów kluczowych wierszy. Pierwszy wiersz każdej unikalnej wartości klucza nie zawiera dodatkowego 4-bajtowego unikalizatora.
Solomon Rutzky
@srutzky, najwyraźniej masz rację. Nigdy nie myślałem, że SQL Server pozwoli na klucze o zmiennej szerokości. To jest brzydkie. Wydajny, tak, ale brzydki.
Roger Wolf,