Ich hatte den Eindruck, DATALENGTH()
dass ich die Gesamtgröße der Tabelle erhalten würde , wenn ich alle Felder für alle Datensätze in einer Tabelle summieren würde. Irre ich mich
SELECT
SUM(DATALENGTH(Field1)) +
SUM(DATALENGTH(Field2)) +
SUM(DATALENGTH(Field3)) TotalSizeInBytes
FROM SomeTable
WHERE X, Y, and Z are true
Ich habe diese Abfrage unten (die ich online erhalten habe, um Tabellengrößen und Clustered-Indizes zu erhalten, damit sie keine NC-Indizes enthält) verwendet, um die Größe einer bestimmten Tabelle in meiner Datenbank zu ermitteln. Für Abrechnungszwecke (wir berechnen unseren Abteilungen den von ihnen genutzten Speicherplatz) muss ich herausfinden, wie viel Speicherplatz jede Abteilung in dieser Tabelle verwendet. Ich habe eine Abfrage, die jede Gruppe in der Tabelle identifiziert. Ich muss nur herausfinden, wie viel Platz jede Gruppe einnimmt.
Der Platz pro Zeile kann aufgrund von VARCHAR(MAX)
Feldern in der Tabelle stark schwanken, daher kann ich nicht einfach eine durchschnittliche Größe * des Zeilenverhältnisses für eine Abteilung annehmen. Wenn ich den DATALENGTH()
oben beschriebenen Ansatz verwende, erhalte ich nur 85% des gesamten in der folgenden Abfrage verwendeten Speicherplatzes. Gedanken?
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
Es wurde vorgeschlagen, für jede Abteilung einen gefilterten Index zu erstellen oder die Tabelle zu partitionieren, damit ich den pro Index verwendeten Speicherplatz direkt abfragen kann. Gefilterte Indizes könnten programmgesteuert erstellt werden (und während eines Wartungsfensters oder wenn ich die regelmäßige Abrechnung durchführen muss), anstatt den Speicherplatz ständig zu verwenden (Partitionen wären in dieser Hinsicht besser).
Ich mag diesen Vorschlag und würde das normalerweise tun. Aber um ehrlich zu sein, benutze ich die "jede Abteilung" als Beispiel, um zu erklären, warum ich das brauche, aber um ehrlich zu sein, das ist nicht wirklich der Grund. Aus Gründen der Vertraulichkeit kann ich den genauen Grund, warum ich diese Daten benötige, nicht erklären, aber es ist analog zu verschiedenen Abteilungen.
Zu den nicht gruppierten Indizes in dieser Tabelle: Wenn ich die Größen der NC-Indizes ermitteln kann, wäre das großartig. Die NC-Indizes machen jedoch <1% der Größe des Clustered-Index aus, sodass wir diese nicht berücksichtigen können. Wie würden wir jedoch die NC-Indizes überhaupt einbeziehen? Ich kann nicht einmal eine genaue Größe für den Clustered-Index erhalten :)
quelle
Antworten:
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.
Daten sind nicht das einzige, was Speicherplatz auf einer 8k-Datenseite beansprucht:
Es ist Platz reserviert. Sie dürfen nur 8060 der 8192 Bytes verwenden (das sind 132 Bytes, die Ihnen überhaupt nicht gehörten):
DBCC PAGE
, weshalb er hier getrennt gehalten wird, anstatt in den nachstehenden Informationen pro Zeile enthalten zu sein.NULL
. 1 Byte pro Satz von 8 Spalten. Und für alle Spalten, auch für dieNOT NULL
. Daher mindestens 1 Byte.ALLOW_SNAPSHOT_ISOLATION ON
oder eingestellt istREAD_COMMITTED_SNAPSHOT ON
).LOB-Zeiger für Daten, die nicht in einer Zeile gespeichert sind. Das würde also
DATALENGTH
+ pointer_size ausmachen. Diese haben jedoch keine Standardgröße. Weitere Informationen zu diesem komplexen Thema finden Sie im folgenden Blog-Beitrag: Wie groß ist der LOB-Zeiger für (MAX) Typen wie Varchar, Varbinary usw.? . Zwischen diesem verknüpften Beitrag und einigen zusätzlichen Tests, die ich durchgeführt habe , sollten die (Standard-) Regeln wie folgt lauten:TEXT
,NTEXT
undIMAGE
):text in row
Option festzulegen , dann:VARCHAR(MAX)
,NVARCHAR(MAX)
undVARBINARY(MAX)
):large value types out of row
Option, dann verwenden Sie immer ein 16-Byte - Zeiger auf LOB Speicher.LOB-Überlaufseiten: Wenn ein Wert 10.000 beträgt, ist dafür eine vollständige 8.000-Seite Überlauf und dann ein Teil einer zweiten Seite erforderlich. Wenn keine anderen Daten den verbleibenden Speicherplatz belegen können (oder sogar dürfen, da bin ich mir dieser Regel nicht sicher), haben Sie ca. 6 KB "verschwendeten" Speicherplatz auf dieser 2. LOB-Überlauf-Datenseite.
Nicht genutzter Speicherplatz: Eine 8k-Datenseite ist genau das: 8192 Bytes. Es variiert nicht in der Größe. Die darauf platzierten Daten und Metadaten passen jedoch nicht immer gut in alle 8192 Bytes. Und Zeilen können nicht auf mehrere Datenseiten aufgeteilt werden. Wenn Sie also noch 100 Bytes haben, aber keine Zeile (oder keine Zeile, die in Abhängigkeit von mehreren Faktoren an diesen Speicherort passen würde) dort passen kann, belegt die Datenseite immer noch 8192 Bytes, und Ihre zweite Abfrage zählt nur die Anzahl von Datenseiten. Sie können diesen Wert an zwei Stellen finden (denken Sie daran, dass ein Teil dieses Werts ein Teil des reservierten Speicherplatzes ist):
DBCC PAGE( db_name, file_id, page_id ) WITH TABLERESULTS;
Suchen Sie nachParentObject
= "PAGE HEADER:" undField
= "m_freeCnt". DasValue
Feld gibt die Anzahl der nicht verwendeten Bytes an.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;
Dies ist der gleiche Wert wie von "m_freeCnt" angegeben. Dies ist einfacher als DBCC, da es viele Seiten erhalten kann, erfordert aber auch, dass die Seiten zuerst in den Pufferpool eingelesen wurden.Von
FILLFACTOR
<100 reservierterFILLFACTOR
Speicherplatz . Neu erstellte Seiten berücksichtigen die Einstellung nicht. Bei einem REBUILD wird dieser Speicherplatz jedoch auf jeder Datenseite reserviert. Die Idee hinter dem reservierten Speicherplatz ist, dass er von nicht sequentiellen Einfügungen und / oder Aktualisierungen verwendet wird, die die Größe der Zeilen auf der Seite bereits erweitern, da Spalten variabler Länge mit etwas mehr Daten aktualisiert werden (aber nicht genug, um a zu verursachen) Seitenaufteilung). Sie können jedoch problemlos Speicherplatz auf Datenseiten reservieren, die natürlich niemals neue Zeilen erhalten und die vorhandenen Zeilen niemals aktualisieren oder zumindest nicht auf eine Weise aktualisiert werden, die die Größe der Zeile erhöht.Seitenteilung (Fragmentierung): Wenn Sie eine Zeile an einer Stelle hinzufügen müssen, an der kein Platz für die Zeile vorhanden ist, wird die Seite geteilt. In diesem Fall werden ca. 50% der vorhandenen Daten auf eine neue Seite verschoben und die neue Zeile zu einer der beiden Seiten hinzugefügt. Aber Sie haben jetzt etwas mehr freien Speicherplatz, der nicht durch
DATALENGTH
Berechnungen berücksichtigt wird.Zum Löschen markierte Zeilen. Wenn Sie Zeilen löschen, werden diese nicht immer sofort von der Datenseite entfernt. Wenn sie nicht sofort entfernt werden können, sind sie "für den Tod markiert" (Steven Segal-Referenz) und werden später durch den Geisterbereinigungsprozess physisch entfernt (ich glaube, das ist der Name). Diese sind jedoch möglicherweise für diese spezielle Frage nicht relevant.
Ghost-Seiten? Ich bin mir nicht sicher, ob dies der richtige Begriff ist, aber manchmal werden Datenseiten erst entfernt, wenn eine Neuerstellung des Clustered-Index durchgeführt wurde. Das würde auch mehr Seiten ausmachen, als
DATALENGTH
sich summieren würden. Dies sollte im Allgemeinen nicht passieren, aber ich bin vor einigen Jahren einmal darauf gestoßen.SPARSE-Spalten: Sparse-Spalten sparen Platz (hauptsächlich für Datentypen mit fester Länge) in Tabellen, in denen ein großer Teil der Zeilen
NULL
für eine oder mehrere Spalten bestimmt ist. DieSPARSE
Option macht denNULL
Werttyp mit 0 Bytes (anstelle der normalen fester Länge Menge, wie beispielsweise 4 Bytes für eineINT
), aber , nicht-NULL nehmen jeweils Werte bis weitere 4 Bytes für feste Länge Typen und einen variablen Betrag für Typen mit variabler Länge. Das Problem hierbei ist, dassDATALENGTH
die zusätzlichen 4 Bytes für Nicht-NULL-Werte in einer SPARSE-Spalte nicht enthalten sind. Daher müssen diese 4 Bytes wieder hinzugefügt werden. Sie können überprüfen, obSPARSE
Spalten vorhanden sind über:SPARSE
Aktualisieren Sie dann für jede Spalte die ursprüngliche Abfrage, um Folgendes zu verwenden:Bitte beachten Sie, dass die obige Berechnung zum Hinzufügen von 4 Standardbytes etwas vereinfacht ist, da sie nur für Typen mit fester Länge funktioniert. UND, es gibt zusätzliche Metadaten pro Zeile (soweit ich das beurteilen kann), die den verfügbaren Speicherplatz für Daten reduzieren, indem einfach mindestens eine SPARSE-Spalte vorhanden ist. Weitere Informationen finden Sie auf der MSDN-Seite für die Verwendung sparsamer Spalten .
Index- und andere (z. B. IAM-, PFS-, GAM-, SGAM- usw.) Seiten: Dies sind keine "Datenseiten" in Bezug auf Benutzerdaten. Dadurch wird die Gesamtgröße des Tisches aufgeblasen. Wenn Sie SQL Server 2012 oder höher verwenden, können Sie die
sys.dm_db_database_page_allocations
Dynamic Management Function (DMF) verwenden, um die Seitentypen anzuzeigen (frühere Versionen von SQL Server können Folgendes verwendenDBCC IND(0, N'dbo.table_name', 0);
):Weder das
DBCC IND
nochsys.dm_db_database_page_allocations
(mit dieser WHERE-Klausel) meldet Indexseiten, und nur dasDBCC IND
meldet mindestens eine IAM-Seite.DATA_COMPRESSION: Wenn Sie
ROW
oder diePAGE
Komprimierung für den Clustered Index oder Heap aktiviert haben , können Sie das meiste vergessen, was bisher erwähnt wurde. Der 96-Byte-Seitenkopf, das Slot-Array mit 2 Bytes pro Zeile und die Versionsinformationen für 14 Bytes pro Zeile sind noch vorhanden, aber die physische Darstellung der Daten wird sehr komplex (viel mehr als bei der Komprimierung bereits erwähnt) wird nicht verwendet). Bei der Zeilenkomprimierung versucht SQL Server beispielsweise, den kleinstmöglichen Container für jede Spalte pro Zeile zu verwenden. Wenn Sie also eine haben , werden nur 2 Bytes benötigt. Ganzzahlige Typen, die entweder oder keinen Platz beanspruchen und einfach als oder "leer" angezeigt werden (dhBIGINT
Spalte haben, die andernfalls (vorausgesetzt, sieSPARSE
ist auch nicht aktiviert) immer 8 Bytes belegt, wenn der Wert zwischen -128 und 127 liegt (dh eine vorzeichenbehaftete 8-Bit-Ganzzahl), wird nur 1 Byte verwendet, und wenn die Wert könnte in eine passenSMALLINT
NULL
0
NULL
0
) in einem Array, das die Spalten abbildet. Und es gibt viele, viele andere Regeln. Haben Sie Unicode-Daten (NCHAR
,NVARCHAR(1 - 4000)
aber nichtNVARCHAR(MAX)
, auch wenn sie in einer Reihe gespeichert sind)? Die Unicode-Komprimierung wurde in SQL Server 2008 R2 hinzugefügt, es gibt jedoch keine Möglichkeit, das Ergebnis des "komprimierten" Werts in allen Situationen vorherzusagen, ohne die tatsächliche Komprimierung durchzuführen, da die Regeln komplex sind .Ihre zweite Abfrage ist zwar genauer in Bezug auf den gesamten physischen Speicherplatz auf der Festplatte, aber nur dann wirklich genau, wenn Sie einen
REBUILD
Clustered-Index erstellen. Und danach müssen Sie noch alle berücksichtigenFILLFACTOR
Einstellungen unter 100 . Und selbst dann gibt es immer Seitenkopfzeilen und oft genug eine Menge "verschwendeten" Speicherplatzes, der einfach nicht ausfüllbar ist, weil er zu klein ist, um in eine Zeile zu passen Tabelle oder zumindest die Zeile, die logischerweise in diesen Steckplatz gehen soll.In Bezug auf die Genauigkeit der zweiten Abfrage bei der Bestimmung der "Datennutzung" erscheint es am fairsten, die Seitenkopf-Bytes zurückzusetzen, da es sich nicht um Datennutzung handelt: Es handelt sich um Betriebskosten. Wenn eine Datenseite 1 Zeile enthält und diese Zeile nur eine ist
TINYINT
, ist für dieses 1 Byte immer noch erforderlich, dass die Datenseite vorhanden ist, und daher die 96 Bytes des Headers. Sollte diese 1 Abteilung für die gesamte Datenseite belastet werden? Wenn diese Datenseite dann von Abteilung 2 ausgefüllt wird, würden sie diese "Gemeinkosten" gleichmäßig aufteilen oder proportional zahlen? Scheint am einfachsten, es einfach zurückzuziehen. In diesem Fall ist die Verwendung eines Wertes von8
zum Multiplizierennumber of pages
zu hoch. Wie wäre es mit:Verwenden Sie daher etwas wie:
für alle Berechnungen für "number_of_pages" -Spalten.
UND , wenn man bedenkt, dass die Verwendung
DATALENGTH
pro Feld die Metadaten pro Zeile nicht zurückgeben kann, sollte dies zu Ihrer Abfrage pro Tabelle hinzugefügt werden, in der Sie die DatenDATALENGTH
pro Feld erhalten und nach jeder "Abteilung" filtern:ALLOW_SNAPSHOT_ISOLATION
oderREAD_COMMITTED_SNAPSHOT
gesetzt hatON
)NULL
, und wenn der Wert in die Zeile passt, kann er viel kleiner oder viel größer als der Zeiger sein, und wenn der Wert nicht gespeichert ist. Zeile, dann kann die Größe des Zeigers davon abhängen, wie viele Daten vorhanden sind. Da wir jedoch nur eine Schätzung wollen (dh "swag"), scheint es, dass 24 Bytes ein guter Wert sind (na ja, so gut wie jeder andere ;-). Dies ist proMAX
Feld.Verwenden Sie daher etwas wie:
Im Allgemeinen (Zeilenüberschrift + Anzahl der Spalten + Slot-Array + NULL-Bitmap):
Im Allgemeinen (automatische Erkennung, wenn "Versionsinformationen" vorhanden sind):
Wenn Spalten mit variabler Länge vorhanden sind, fügen Sie Folgendes hinzu:
Wenn es irgendwelche
MAX
/ LOB-Spalten gibt, fügen Sie hinzu:Allgemein:
Dies ist nicht genau und funktioniert auch dann nicht, wenn Sie die Zeilen- oder Seitenkomprimierung für den Heap- oder Clustered-Index aktiviert haben, sollte Sie aber auf jeden Fall näher bringen.
UPDATE In Bezug auf das 15% Unterschiedsgeheimnis
Wir (ich selbst eingeschlossen) waren so darauf konzentriert, darüber nachzudenken, wie Datenseiten angeordnet sind und wie
DATALENGTH
Dinge berücksichtigt werden könnten, dass wir nicht viel Zeit damit verbracht haben, die zweite Abfrage zu überprüfen. Ich habe diese Abfrage für eine einzelne Tabelle ausgeführt und diese Werte dann mit den Werten verglichen, die von gemeldet wurden,sys.dm_db_database_page_allocations
und sie waren nicht dieselben Werte für die Anzahl der Seiten. Aus einer Ahnung heraus entfernte ich die Aggregatfunktionen undGROUP BY
und ersetzte dieSELECT
Liste durcha.*, '---' AS [---], p.*
. Und dann wurde klar: Die Leute müssen vorsichtig sein, woher sie in diesen düsteren Interwebs ihre Informationen und Skripte beziehen ;-). Die zweite in der Frage gepostete Abfrage ist nicht genau korrekt, insbesondere für diese spezielle Frage.Kleines Problem: Abgesehen davon, dass es nicht viel Sinn macht
GROUP BY rows
(und diese Spalte nicht in einer Aggregatfunktion hat), ist die Verbindung zwischensys.allocation_units
undsys.partitions
technisch nicht korrekt. Es gibt drei Arten von Zuordnungseinheiten, von denen eine einem anderen Feld beitreten sollte. Sehr oftpartition_id
undhobt_id
gleich, daher gibt es möglicherweise nie ein Problem, aber manchmal haben diese beiden Felder unterschiedliche Werte.Hauptproblem: Die Abfrage verwendet das
used_pages
Feld. Dieses Feld deckt alle Arten von Seiten ab: Daten, Index, IAM usw. usw. Es gibt ein anderes, geeigneteres Feld, das nur für die tatsächlichen Daten verwendet werden kann :data_pages
.Ich habe die zweite Abfrage in der Frage unter Berücksichtigung der oben genannten Punkte und unter Verwendung der Datenseitengröße angepasst, die den Seitenkopf zurücksetzt. Ich habe auch zwei unnötige JOINs entfernt:
sys.schemas
(ersetzt durch call toSCHEMA_NAME()
) undsys.indexes
(der Clustered Index ist immerindex_id = 1
und wir habenindex_id
insys.partitions
).quelle
Vielleicht ist dies eine Grunge-Antwort, aber das würde ich tun.
Die DATALENGTH macht also nur 86% der Gesamtmenge aus. Es ist immer noch sehr repräsentativ aufgeteilt. Der Overhead in der exzellenten Antwort von Srutzky sollte ziemlich gleichmäßig aufgeteilt sein.
Ich würde Ihre zweite Abfrage (Seiten) für die Summe verwenden. Verwenden Sie die erste (Datenlänge), um die Aufteilung zuzuweisen. Viele Kosten werden durch eine Normalisierung zugeordnet.
Und Sie müssen bedenken, dass eine genauere Antwort die Kosten erhöhen wird, sodass selbst die Abteilung, die bei einem Split verloren hat, möglicherweise noch mehr zahlt.
quelle