Miałem wrażenie, że gdybym zsumował DATALENGTH()
wszystkie pola dla wszystkich rekordów w tabeli, uzyskałbym całkowity rozmiar tabeli. Czy się mylę?
SELECT
SUM(DATALENGTH(Field1)) +
SUM(DATALENGTH(Field2)) +
SUM(DATALENGTH(Field3)) TotalSizeInBytes
FROM SomeTable
WHERE X, Y, and Z are true
Użyłem tego zapytania poniżej (które dostałem z Internetu, aby uzyskać rozmiary tabel, tylko indeksy klastrowe, więc nie zawiera indeksów NC), aby uzyskać rozmiar określonej tabeli w mojej bazie danych. Do celów rozliczeniowych (obciążamy nasze działy ilością wykorzystanego miejsca) muszę dowiedzieć się, ile miejsca każdy dział wykorzystał w tej tabeli. Mam zapytanie, które identyfikuje każdą grupę w tabeli. Muszę tylko dowiedzieć się, ile miejsca zajmuje każda grupa.
Przestrzeń na wiersz może gwałtownie się wahać z powodu VARCHAR(MAX)
pól w tabeli, więc nie mogę po prostu przyjąć średniego rozmiaru * stosunku rzędów dla działu. Kiedy korzystam z DATALENGTH()
opisanego powyżej podejścia, otrzymuję tylko 85% całkowitej przestrzeni użytej w poniższym zapytaniu. Myśli?
SELECT
s.Name AS SchemaName,
t.NAME AS TableName,
p.rows AS RowCounts,
(SUM(a.total_pages) * 8)/1024 AS TotalSpaceMB,
(SUM(a.used_pages) * 8)/1024 AS UsedSpaceMB,
((SUM(a.total_pages) - SUM(a.used_pages)) * 8)/1024 AS UnusedSpaceMB
FROM
sys.tables t with (nolock)
INNER JOIN
sys.schemas s with (nolock) ON s.schema_id = t.schema_id
INNER JOIN
sys.indexes i with (nolock) ON t.OBJECT_ID = i.object_id
INNER JOIN
sys.partitions p with (nolock) ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
INNER JOIN
sys.allocation_units a with (nolock) ON p.partition_id = a.container_id
WHERE
t.is_ms_shipped = 0
AND i.OBJECT_ID > 255
AND i.type_desc = 'Clustered'
GROUP BY
t.Name, s.Name, p.Rows
ORDER BY
TotalSpaceMB desc
Zasugerowano, że utworzę filtrowany indeks dla każdego działu lub partycji tabeli, dzięki czemu mogę bezpośrednio przeszukiwać zajętą przestrzeń na indeks. Filtrowane indeksy można tworzyć programowo (i upuszczać je ponownie podczas okna konserwacji lub gdy muszę wykonywać okresowe rozliczenia), zamiast cały czas używać miejsca (partycje byłyby lepsze pod tym względem).
Podoba mi się ta sugestia i zwykle to robię. Ale szczerze mówiąc, używam „każdego działu” jako przykładu, aby wyjaśnić, dlaczego tego potrzebuję, ale szczerze mówiąc, to nie jest tak naprawdę powód. Ze względu na poufność nie mogę wyjaśnić dokładnego powodu, dla którego potrzebuję tych danych, ale jest to analogiczne do różnych działów.
Jeśli chodzi o indeksy nieklastrowane w tej tabeli: Gdybym mógł uzyskać rozmiary indeksów NC, byłoby świetnie. Jednak indeksy NC odpowiadają za <1% wielkości indeksu klastrowanego, więc nie możemy ich uwzględniać. Jak jednak w ogóle uwzględnilibyśmy indeksy NC? Nie mogę nawet uzyskać dokładnego rozmiaru indeksu klastrowanego :)
źródło
Odpowiedzi:
Please note that the following info is not intended to be a comprehensive
description of how data pages are laid out, such that one can calculate
the number of bytes used per any set of rows, as that is very complicated.
Dane nie są jedyną rzeczą zajmującą miejsce na stronie z danymi 8k:
Jest zarezerwowane miejsce. Możesz używać tylko 8060 z 8192 bajtów (to 132 bajty, które nigdy nie były twoje):
DBCC PAGE
, dlatego jest tutaj trzymana osobno, zamiast uwzględniać ją w informacjach dla poszczególnych wierszy poniżej.NULL
. 1 bajt na każdy zestaw 8 kolumn. I dla wszystkich kolumn, nawetNOT NULL
tych. Stąd minimum 1 bajt.ALLOW_SNAPSHOT_ISOLATION ON
lub dwieREAD_COMMITTED_SNAPSHOT ON
).Wskaźniki LOB dla danych, które nie są przechowywane w wierszu. To by odpowiadało
DATALENGTH
+ pointer_size. Ale nie są to standardowe rozmiary. Zobacz następujący post na blogu, aby uzyskać szczegółowe informacje na temat tego złożonego tematu: Jaki jest rozmiar wskaźnika LOB dla typów (MAX), takich jak Varchar, Varbinary, itp.? . Pomiędzy tym połączonym postem a kilkoma dodatkowymi testami, które przeprowadziłem , (domyślne) reguły powinny wyglądać następująco:TEXT
,NTEXT
, iIMAGE
):text in row
opcji, to:VARCHAR(MAX)
,NVARCHAR(MAX)
, iVARBINARY(MAX)
):large value types out of row
, zawsze używaj 16-bajtowego wskaźnika do pamięci LOB.Strony przepełnienia LOB: Jeśli wartość wynosi 10 000, będzie to wymagało 1 pełnej strony przepełnienia 8 000, a następnie części drugiej strony. Jeśli żadne inne dane nie mogą zająć pozostałej przestrzeni (lub nawet jest to dozwolone, nie jestem pewien tej zasady), masz około 6 KB „zmarnowanego” miejsca na tym drugim arkuszu danych przepełnienia LOB.
Niewykorzystane miejsce: strona danych o wielkości 8 tys. To po prostu: 8192 bajtów. Nie różni się rozmiarem. Umieszczone na nim dane i meta-dane nie zawsze jednak dobrze pasują do wszystkich 8192 bajtów. Wierszy nie można podzielić na wiele stron danych. Jeśli więc pozostało 100 bajtów, ale żaden wiersz (lub żaden wiersz, który pasowałby do tej lokalizacji, w zależności od kilku czynników) nie może się tam zmieścić, strona danych nadal zajmuje 8192 bajtów, a twoje drugie zapytanie liczy tylko liczbę strony danych. Możesz znaleźć tę wartość w dwóch miejscach (pamiętaj, że część tej wartości to pewna ilość zarezerwowanego miejsca):
DBCC PAGE( db_name, file_id, page_id ) WITH TABLERESULTS;
PoszukajParentObject
= "PAGE HEADER:" iField
= "m_freeCnt".Value
Pole jest liczba nieużywanych bajtów.SELECT buff.free_space_in_bytes FROM sys.dm_os_buffer_descriptors buff WHERE buff.[database_id] = DB_ID(N'db_name') AND buff.[page_id] = page_id;
Jest to ta sama wartość, co zgłoszona przez „m_freeCnt”. Jest to łatwiejsze niż DBCC, ponieważ może uzyskać wiele stron, ale przede wszystkim wymaga, aby strony zostały wczytane do puli buforów.Miejsce zarezerwowane przez
FILLFACTOR
<100. Nowo utworzone strony nie respektują tegoFILLFACTOR
ustawienia, ale wykonanie operacji REBUILD zarezerwuje to miejsce na każdej stronie danych. Idea zarezerwowanego miejsca polega na tym, że będzie on używany przez niesekwencyjne wstawki i / lub aktualizacje, które już zwiększają rozmiar wierszy na stronie, ponieważ kolumny o zmiennej długości są aktualizowane o nieco więcej danych (ale niewystarczająco, aby spowodować podział strony). Ale możesz z łatwością zarezerwować miejsce na stronach danych, które naturalnie nigdy nie otrzymają nowych wierszy i nigdy nie zaktualizują istniejących wierszy lub przynajmniej nie zaktualizują w sposób, który zwiększyłby rozmiar wiersza.Podziały stron (fragmentacja): Konieczność dodania wiersza do lokalizacji, w której nie ma miejsca na wiersz, spowoduje podział strony. W takim przypadku około 50% istniejących danych zostaje przeniesionych na nową stronę, a nowy wiersz jest dodawany do jednej z 2 stron. Ale teraz masz trochę więcej wolnego miejsca, które nie jest uwzględnione w
DATALENGTH
obliczeniach.Wiersze oznaczone do usunięcia. Po usunięciu wierszy nie zawsze są one natychmiast usuwane ze strony danych. Jeśli nie można ich natychmiast usunąć, są „oznaczeni na śmierć” (odniesienie Stevena Segala) i zostaną później fizycznie usunięte przez proces oczyszczania duchów (wierzę, że tak się nazywa). Mogą one jednak nie mieć związku z tym konkretnym pytaniem.
Strony duchów? Nie jestem pewien, czy jest to właściwy termin, ale czasami strony danych nie są usuwane, dopóki nie zostanie ODBUDOWANY Indeks klastrowany. To również stanowiłoby więcej stron niż
DATALENGTH
suma. To na ogół nie powinno się zdarzyć, ale natknąłem się na to raz, kilka lat temu.SPARSE kolumny: rzadkie kolumny oszczędzają miejsce (głównie dla typów danych o stałej długości) w tabelach, w których duży% wierszy
NULL
dotyczy jednej lub więcej kolumn. TaSPARSE
opcja powoduje, żeNULL
typ wartości zwiększa się o 0 bajtów (zamiast normalnej kwoty o stałej długości, takiej jak 4 bajty dla anINT
), ale wartości inne niż NULL zajmują dodatkowe 4 bajty dla typów o stałej długości i zmienną kwotę dla typy o zmiennej długości. Problem polega na tym,DATALENGTH
że nie zawiera dodatkowych 4 bajtów dla wartości innych niż NULL w kolumnie SPARSE, więc te 4 bajty muszą zostać ponownie dodane. Możesz sprawdzić, czy są jakieśSPARSE
kolumny przez:Następnie dla każdej
SPARSE
kolumny zaktualizuj oryginalne zapytanie, aby użyć:Należy pamiętać, że powyższe obliczenia, aby dodać standardowe 4 bajty, są nieco uproszczone, ponieważ działają tylko dla typów o stałej długości. ORAZ istnieje dodatkowe metadane na wiersz (z tego, co do tej pory mogę powiedzieć), które zmniejszają przestrzeń dostępną dla danych, po prostu przez posiadanie co najmniej jednej kolumny SPARSE. Aby uzyskać więcej informacji, zobacz stronę MSDN dotyczącą użycia rzadkich kolumn .
Indeks i inne strony (np. IAM, PFS, GAM, SGAM itp.): Nie są to strony „danych” pod względem danych użytkownika. Będą to zawyżać całkowity rozmiar stołu. Jeśli używasz programu SQL Server 2012 lub nowszego, możesz użyć funkcji
sys.dm_db_database_page_allocations
dynamicznego zarządzania (DMF), aby wyświetlić typy stron (mogą korzystać z wcześniejszych wersji programu SQL ServerDBCC IND(0, N'dbo.table_name', 0);
):Ani
DBCC IND
norsys.dm_db_database_page_allocations
(z tą klauzulą WHERE) nie zgłosi żadnych stron indeksu i tylkoDBCC IND
ta zgłosi co najmniej jedną stronę IAM.DATA_COMPRESSION: Jeśli masz
ROW
lubPAGE
Kompresja włączona indeks klastrowany lub sterty, to można zapomnieć o większości z tego, co zostało wymienione do tej pory. 96-bajtowy nagłówek strony, tablica szczelin 2 bajtów na wiersz i 14 bajtów na wiersz informacji o wersji są nadal dostępne, ale fizyczna reprezentacja danych staje się bardzo złożona (o wiele bardziej niż to, co już wspomniano podczas kompresji nie jest używany). Na przykład dzięki kompresji wierszy program SQL Server próbuje użyć najmniejszego możliwego kontenera, aby dopasować każdą kolumnę dla każdego wiersza. Więc jeśli maszBIGINT
kolumnę, która inaczej (zakładając, że nieSPARSE
jest również włączona) zawsze zajmuje 8 bajtów, jeśli wartość wynosi od -128 do 127 (tj. 8-bitowa liczba całkowita ze znakiem), wówczas użyje tylko 1 bajtu, a jeśli wartość może zmieścić się wSMALLINT
, zajmie tylko 2 bajty. Typy Integer, które są alboNULL
albo0
zajmują żadnego miejsca i są po prostu oznaczone jakoNULL
albo „pusty” (tj0
) w odwzorowaniu tablicy poza kolumnami. I jest wiele, wiele innych zasad. Czy masz dane Unicode (NCHAR
,NVARCHAR(1 - 4000)
ale nieNVARCHAR(MAX)
, nawet jeśli są przechowywane w wierszu)? Kompresja Unicode została dodana w SQL Server 2008 R2, ale nie można przewidzieć wyniku wartości „skompresowanej” we wszystkich sytuacjach bez faktycznej kompresji, biorąc pod uwagę złożoność reguł .Tak naprawdę, twoje drugie zapytanie, choć bardziej dokładne pod względem całkowitej fizycznej przestrzeni zajmowanej na dysku, jest naprawdę bardzo dokładne tylko po wykonaniu
REBUILD
indeksu klastrowanego. A potem musisz uwzględnić każdeFILLFACTOR
ustawienie poniżej 100. I nawet wtedy zawsze są nagłówki stron, a często wystarczająca ilość „zmarnowanej” przestrzeni, której po prostu nie można wypełnić, ponieważ jest zbyt mała, aby zmieścić się w dowolnym wierszu w tym tabela, a przynajmniej wiersz, który logicznie powinien iść w tym gnieździe.Jeśli chodzi o dokładność drugiego zapytania przy określaniu „wykorzystania danych”, najbardziej sprawiedliwym wydaje się wycofanie bajtów nagłówka strony, ponieważ nie są one wykorzystaniem danych: są to koszty ogólne prowadzenia działalności. Jeśli na stronie danych znajduje się 1 wiersz, a ten wiersz to tylko jeden
TINYINT
, to ten 1 bajt nadal wymagał istnienia strony danych, a zatem 96 bajtów nagłówka. Czy ten 1 dział powinien zostać obciążony za całą stronę z danymi? Jeśli ta strona danych zostanie następnie wypełniona przez Dział 2, czy równomiernie podzielą ten „koszt ogólny”, czy zapłacą proporcjonalnie? Najłatwiej jest to po prostu wycofać. W takim przypadku użycie wartości8
mnożenia przeciwkonumber of pages
jest zbyt wysokie. Co powiesz na:Dlatego użyj czegoś takiego jak:
dla wszystkich obliczeń w kolumnach „liczba_stron”.
ORAZ , biorąc pod uwagę, że użycie
DATALENGTH
dla każdego pola nie może zwrócić metadanych dla wiersza, które należy dodać do zapytania dla tabeli, w którym otrzymujeszDATALENGTH
dla każdego pola, filtrując według każdego „działu”:ALLOW_SNAPSHOT_ISOLATION
czyREAD_COMMITTED_SNAPSHOT
ustawionyON
)NULL
, a jeśli wartość mieści się w wierszu, może być znacznie mniejsza lub znacznie większa niż wskaźnik, a wartość jest przechowywana poza wiersz, a następnie rozmiar wskaźnika może zależeć od ilości danych. Ponieważ jednak chcemy tylko oszacowania (tj. „Swag”), wydaje się, że 24 bajty to dobra wartość do wykorzystania (cóż, tak dobra jak każda inna ;-). To jest dla każdegoMAX
pola.Dlatego użyj czegoś takiego jak:
Ogólnie (nagłówek wiersza + liczba kolumn + tablica boków + mapa bitowa NULL):
Ogólnie (wykrywaj automatycznie, jeśli dostępne są „informacje o wersji”):
JEŻELI istnieją kolumny o zmiennej długości, dodaj:
JEŚLI są jakieś
MAX
kolumny / LOB, dodaj:Ogólnie:
Nie jest to dokładne i znowu nie zadziała, jeśli masz włączoną kompresję wierszy lub strony w indeksie sterty lub klastra, ale zdecydowanie powinno cię to przybliżyć.
AKTUALIZACJA dotycząca tajemnicy 15% różnicy
My (w tym ja) byliśmy tak skoncentrowani na zastanowieniu się, jak układają się strony danych i jak
DATALENGTH
mogą wyjaśniać rzeczy, które nie poświęciliśmy dużo czasu na przeglądanie drugiego zapytania. Uruchomiłem to zapytanie dla pojedynczej tabeli, a następnie porównałem te wartości z tym, co było zgłaszane,sys.dm_db_database_page_allocations
i nie były to te same wartości dla liczby stron. Na przeczucie usunąłem funkcje agregująceGROUP BY
i zastąpiłemSELECT
listęa.*, '---' AS [---], p.*
. A potem stało się jasne: ludzie muszą uważać, skąd na tych mrocznych interwebach czerpią informacje i skrypty ;-). Drugie zapytanie zamieszczone w pytaniu nie jest dokładnie poprawne, szczególnie w przypadku tego konkretnego pytania.Drobny problem: poza tym nie ma większego sensu
GROUP BY rows
(i nie ma tej kolumny w funkcji agregującej), ŁĄCZENIE pomiędzysys.allocation_units
isys.partitions
jest technicznie niepoprawne. Istnieją 3 rodzaje Jednostek Alokacji, a jeden z nich powinien ŁĄCZYĆ się w inne pole. Dość częstopartition_id
ihobt_id
są takie same, więc może nigdy nie być problemu, ale czasami te dwa pola mają różne wartości.Główny problem: zapytanie wykorzystuje
used_pages
pole. To pole obejmuje wszystkie typy stron: dane, indeks, IAM itp., Tc. Jest jeszcze inny, bardziej odpowiednie pole do użycia, gdy dotyczy tylko rzeczywiste dane:data_pages
.Dostosowałem drugie zapytanie w pytaniu, mając na uwadze powyższe elementy, i używając rozmiaru strony danych, który wycofuje nagłówek strony. Usunąłem także dwa złączeń, które były niepotrzebne:
sys.schemas
(zastąpiony wywołaniuSCHEMA_NAME()
) isys.indexes
(indeks Klastra jest zawszeindex_id = 1
i mamyindex_id
wsys.partitions
).źródło
Może to odpowiedź grunge, ale właśnie to bym zrobił.
A zatem DATALENGTH stanowi jedynie 86% całości. Jest to nadal bardzo reprezentatywny podział. Narzut w doskonałej odpowiedzi srutzky powinien mieć dość równy podział.
Użyłbym twojego drugiego zapytania (stron) dla całości. I użyj pierwszej (długości danych) do przydzielenia podziału. Wiele kosztów jest alokowanych przy użyciu normalizacji.
I musisz rozważyć bliższą odpowiedź, która podniesie koszty, więc nawet dept, który stracił na podziale, może nadal zapłacić więcej.
źródło