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.
źródło
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:
Użycie
DBCC PAGE
potwierdza moje obliczenia, pokazując:Record Size 263
(dlatempdb
) iRecord Size 277
(dla bazy danych, która jest ustawiona naALLOW_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 PAGE
potwierdza 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 PAGE
potwierdza 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)
. ZobaczyszVALUE
pokazywanie czegoś wzdłuż linii0x60 MIXED_EXT ALLOCATED 0_PCT_FULL
(kiedy spojrzałem na stół klastrowany) lub0x64 MIXED_EXT ALLOCATED 100_PCT_FULL
patrzą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:
** 14-bajtowa wartość „informacji o wersji” będzie dostępna, jeśli baza danych jest ustawiona na jeden
ALLOW_SNAPSHOT_ISOLATION ON
lubREAD_COMMITTED_SNAPSHOT ON
.źródło
tempdb
? Udało mi się odtworzyć dokładne liczby podane przez OP.ALLOW_SNAPSHOT_ISOLATION
. Właśnie próbowałemtempdb
i zobaczyłem, że nie ma „informacji o wersji”, dlatego pasuje 30 wierszy. Zaktualizuję, aby dodać nowe informacje.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_percent
wartość 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ę.
źródło