ZUSAMMENFASSUNG DER DATALENGTHs, die nicht mit der Tabellengröße von sys.allocation_units übereinstimmen

11

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 :)

Chris Woods
quelle
Sie haben also im Wesentlichen zwei Fragen: (1) Warum stimmt die Summe der Zeilenlängen nicht mit der Berücksichtigung der Größe der gesamten Tabelle durch die Metadaten überein? In der folgenden Antwort wird dies zumindest teilweise angesprochen (und dies kann je nach Version und Funktion schwanken, z. B. Komprimierung, Spaltenspeicher usw.). Und was noch wichtiger ist: (2) Wie können Sie den tatsächlich pro Abteilung genutzten Platz genau bestimmen? Ich weiß nicht, ob Sie das genau tun können - denn für einige der in der Antwort enthaltenen Daten kann nicht festgestellt werden, zu welcher Abteilung sie gehören.
Aaron Bertrand
Ich glaube nicht, dass das Problem darin besteht, dass Sie keine genaue Größe für den Clustered-Index haben - die Metadaten sagen Ihnen definitiv genau, wie viel Speicherplatz Ihr Index einnimmt. Was die Metadaten nicht dazu gedacht sind, Ihnen - zumindest in Anbetracht Ihres aktuellen Designs / Ihrer aktuellen Struktur - zu sagen, wie viele Daten jeder Abteilung zugeordnet sind.
Aaron Bertrand

Antworten:

19

                          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):

    • Seitenkopf: Dies sind genau 96 Bytes.
    • Slot-Array: Dies sind 2 Bytes pro Zeile und gibt den Versatz an, an dem jede Zeile auf der Seite beginnt. Die Größe dieses Arrays ist nicht auf die verbleibenden 36 Bytes beschränkt (132 - 96 = 36), andernfalls können Sie effektiv nur maximal 18 Zeilen auf eine Datenseite setzen. Dies bedeutet, dass jede Zeile 2 Byte größer ist als Sie denken. Dieser Wert ist nicht in der "Datensatzgröße" enthalten, wie von angegeben DBCC PAGE, weshalb er hier getrennt gehalten wird, anstatt in den nachstehenden Informationen pro Zeile enthalten zu sein.
    • Pro-Zeile-Metadaten (einschließlich, aber nicht beschränkt auf):
      • Die Größe variiert je nach Tabellendefinition (dh Anzahl der Spalten, variable Länge oder feste Länge usw.). Informationen aus den Kommentaren von @ PaulWhite und @ Aaron, die in der Diskussion zu dieser Antwort zu finden sind und den Tests zu finden sind.
      • Zeilenüberschrift: 4 Bytes, von denen 2 den Datensatztyp angeben und die anderen beiden einen Offset zur NULL-Bitmap darstellen
      • Anzahl der Spalten: 2 Bytes
      • NULL Bitmap: welche Spalten sind zur Zeit NULL. 1 Byte pro Satz von 8 Spalten. Und für alle Spalten, auch für die NOT NULL. Daher mindestens 1 Byte.
      • Spaltenversatzarray mit variabler Länge: mindestens 4 Byte. 2 Bytes für die Anzahl der Spalten mit variabler Länge und dann 2 Bytes pro Spalte mit variabler Länge für den Versatz an der Stelle, an der er beginnt.
      • Versionsinformationen: 14 Bytes (diese sind vorhanden, wenn Ihre Datenbank auf entweder ALLOW_SNAPSHOT_ISOLATION ONoder eingestellt ist READ_COMMITTED_SNAPSHOT ON).
    • Weitere Informationen hierzu finden Sie in der folgenden Frage und Antwort: Slot-Array und Gesamtseitengröße
    • Bitte lesen Sie den folgenden Blog-Beitrag von Paul Randall, der einige interessante Details zum Aufbau der Datenseiten enthält : Stöbern mit DBCC-SEITE (Teil 1 von?)
  • 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:

    • Legacy / veraltete LOB - Typen , dass niemand mehr als von SQL Server 2005 verwenden soll ( TEXT, NTEXTund IMAGE):
      • Speichern Sie ihre Daten standardmäßig immer auf LOB-Seiten und verwenden Sie immer einen 16-Byte-Zeiger auf den LOB-Speicher.
      • WENN sp_tableoption verwendet wurde, um die text in rowOption festzulegen , dann:
        • wenn auf der Seite Platz zum Speichern des Werts vorhanden ist, und Wert nicht größer als die maximale Zeilengröße ist (konfigurierbarer Bereich von 24 bis 7000 Byte mit einem Standardwert von 256), wird er in der Zeile gespeichert.
        • Andernfalls handelt es sich um einen 16-Byte-Zeiger.
    • Für die neueren LOB - Typen in SQL Server 2005 eingeführt ( VARCHAR(MAX), NVARCHAR(MAX)und VARBINARY(MAX)):
      • Standardmäßig:
        • Wenn der Wert nicht größer als 8000 Byte ist und auf der Seite Platz vorhanden ist, wird er in einer Reihe gespeichert.
        • Inline-Root - Für Daten zwischen 8001 und 40.000 (wirklich 42.000) Bytes, sofern der Speicherplatz dies zulässt, befinden sich 1 bis 5 Zeiger (24 - 72 Bytes) IN ROW, die direkt auf die LOB-Seite (n) verweisen. 24 Bytes für die erste 8k-LOB-Seite und 12 Bytes für jede weitere 8k-Seite für bis zu vier weitere 8k-Seiten.
        • TEXT_TREE - Für Daten über 42.000 Byte oder wenn die 1 bis 5 Zeiger nicht in eine Zeile passen, gibt es nur einen 24-Byte-Zeiger auf die Startseite einer Liste von Zeigern auf die LOB-Seiten (dh den "text_tree" " Seite).
      • IF sp_tableoption verwendet wurde , die festlegen large value types out of rowOption, dann verwenden Sie immer ein 16-Byte - Zeiger auf LOB Speicher.
    • Ich sagte : „default“ Regeln , weil ich habe nicht in-Zeilenwerte gegen die Auswirkungen bestimmter Funktionen testen, wie Datenkomprimierung, Verschlüsselung auf Spaltenebene, Transparent Data Encryption, immer verschlüsselt, usw.
  • 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 nach ParentObject= "PAGE HEADER:" und Field= "m_freeCnt". Das ValueFeld 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 reservierter FILLFACTORSpeicherplatz . 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 DATALENGTHBerechnungen 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 DATALENGTHsich 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 NULLfür eine oder mehrere Spalten bestimmt ist. Die SPARSEOption macht den NULLWerttyp mit 0 Bytes (anstelle der normalen fester Länge Menge, wie beispielsweise 4 Bytes für eine INT), 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, dass DATALENGTHdie 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, ob SPARSESpalten vorhanden sind über:

    SELECT OBJECT_SCHEMA_NAME(sc.[object_id]) AS [SchemaName],
           OBJECT_NAME(sc.[object_id]) AS [TableName],
           sc.name AS [ColumnName]
    FROM   sys.columns sc
    WHERE  sc.is_sparse = 1;

    SPARSEAktualisieren Sie dann für jede Spalte die ursprüngliche Abfrage, um Folgendes zu verwenden:

    SUM(DATALENGTH(FieldN) + 4)

    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_allocationsDynamic Management Function (DMF) verwenden, um die Seitentypen anzuzeigen (frühere Versionen von SQL Server können Folgendes verwenden DBCC IND(0, N'dbo.table_name', 0);):

    SELECT *
    FROM   sys.dm_db_database_page_allocations(
                   DB_ID(),
                   OBJECT_ID(N'dbo.table_name'),
                   1,
                   NULL,
                   N'DETAILED'
                  )
    WHERE  page_type = 1; -- DATA_PAGE

    Weder das DBCC INDnoch sys.dm_db_database_page_allocations(mit dieser WHERE-Klausel) meldet Indexseiten, und nur das DBCC INDmeldet mindestens eine IAM-Seite.

  • DATA_COMPRESSION: Wenn Sie ROWoder die PAGEKomprimierung 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, sie SPARSEist 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 passenSMALLINTNULL0NULL0) in einem Array, das die Spalten abbildet. Und es gibt viele, viele andere Regeln. Haben Sie Unicode-Daten ( NCHAR, NVARCHAR(1 - 4000)aber nicht NVARCHAR(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 REBUILDClustered-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 von 8zum Multiplizieren number of pageszu hoch. Wie wäre es mit:

-- 8192 byte data page - 96 byte header = 8096 (approx) usable bytes.
SELECT 8060.0 / 1024 -- 7.906250

Verwenden Sie daher etwas wie:

(SUM(a.total_pages) * 7.91) / 1024 AS [TotalSpaceMB]

für alle Berechnungen für "number_of_pages" -Spalten.

UND , wenn man bedenkt, dass die Verwendung DATALENGTHpro Feld die Metadaten pro Zeile nicht zurückgeben kann, sollte dies zu Ihrer Abfrage pro Tabelle hinzugefügt werden, in der Sie die Daten DATALENGTHpro Feld erhalten und nach jeder "Abteilung" filtern:

  • Datensatztyp und Offset zu NULL Bitmap: 4 Bytes
  • Spaltenanzahl: 2 Bytes
  • Slot Array: 2 Bytes (nicht in "Datensatzgröße" enthalten, müssen aber noch berücksichtigt werden)
  • NULL-Bitmap: 1 Byte pro 8 Spalten (für alle Spalten)
  • Zeilenversionierung: 14 Bytes (wenn die Datenbank entweder ALLOW_SNAPSHOT_ISOLATIONoder READ_COMMITTED_SNAPSHOTgesetzt hat ON)
  • Offset-Array für Spalten variabler Länge: 0 Byte, wenn alle Spalten eine feste Länge haben. Wenn Spalten eine variable Länge haben, dann 2 Bytes plus 2 Bytes pro Spalte mit variabler Länge.
  • LOB-Zeiger: Dieser Teil ist sehr ungenau, da es keinen Zeiger gibt, wenn der Wert ist 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 pro MAXFeld.

Verwenden Sie daher etwas wie:

  • Im Allgemeinen (Zeilenüberschrift + Anzahl der Spalten + Slot-Array + NULL-Bitmap):

    ([RowCount] * (( 4 + 2 + 2 + (1 + (({NumColumns} - 1) / 8) ))
  • Im Allgemeinen (automatische Erkennung, wenn "Versionsinformationen" vorhanden sind):

    + (SELECT CASE WHEN snapshot_isolation_state = 1 OR is_read_committed_snapshot_on = 1
                     THEN 14 ELSE 0 END FROM sys.databases WHERE [database_id] = DB_ID())
  • Wenn Spalten mit variabler Länge vorhanden sind, fügen Sie Folgendes hinzu:

    + 2 + (2 * {NumVariableLengthColumns})
  • Wenn es irgendwelche MAX/ LOB-Spalten gibt, fügen Sie hinzu:

    + (24 * {NumLobColumns})
  • Allgemein:

    )) AS [MetaDataBytes]

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 DATALENGTHDinge 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_allocationsund sie waren nicht dieselben Werte für die Anzahl der Seiten. Aus einer Ahnung heraus entfernte ich die Aggregatfunktionen und GROUP BYund ersetzte die SELECTListe durch a.*, '---' 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 zwischen sys.allocation_unitsund sys.partitionstechnisch nicht korrekt. Es gibt drei Arten von Zuordnungseinheiten, von denen eine einem anderen Feld beitreten sollte. Sehr oft partition_idund hobt_idgleich, daher gibt es möglicherweise nie ein Problem, aber manchmal haben diese beiden Felder unterschiedliche Werte.

  • Hauptproblem: Die Abfrage verwendet das used_pagesFeld. 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 to SCHEMA_NAME()) und sys.indexes(der Clustered Index ist immer index_id = 1und wir haben index_idin sys.partitions).

SELECT  SCHEMA_NAME(st.[schema_id]) AS [SchemaName],
        st.[name] AS [TableName],
        SUM(sp.[rows]) AS [RowCount],
        (SUM(sau.[total_pages]) * 8.0) / 1024 AS [TotalSpaceMB],
        (SUM(CASE sau.[type]
           WHEN 1 THEN sau.[data_pages]
           ELSE (sau.[used_pages] - 1) -- back out the IAM page
         END) * 7.91) / 1024 AS [TotalActualDataMB]
FROM        sys.tables st
INNER JOIN  sys.partitions sp
        ON  sp.[object_id] = st.[object_id]
INNER JOIN  sys.allocation_units sau
        ON  (   sau.[type] = 1
            AND sau.[container_id] = sp.[partition_id]) -- IN_ROW_DATA
        OR  (   sau.[type] = 2
            AND sau.[container_id] = sp.[hobt_id]) -- LOB_DATA
        OR  (   sau.[type] = 3
            AND sau.[container_id] = sp.[partition_id]) -- ROW_OVERFLOW_DATA
WHERE       st.is_ms_shipped = 0
--AND         sp.[object_id] = OBJECT_ID(N'dbo.table_name')
AND         sp.[index_id] < 2 -- 1 = Clustered Index; 0 = Heap
GROUP BY    SCHEMA_NAME(st.[schema_id]), st.[name]
ORDER BY    [TotalSpaceMB] DESC;
Solomon Rutzky
quelle
Kommentare sind nicht für eine ausführliche Diskussion gedacht. Dieses Gespräch wurde in den Chat verschoben .
Paul White 9
Obwohl die aktualisierte Abfrage, die Sie für die 2. Abfrage bereitgestellt haben, noch weiter entfernt ist (jetzt in die andere Richtung :)), bin ich mit dieser Antwort einverstanden. Dies ist anscheinend eine sehr schwierige Nuss, und für das, was es wert ist, bin ich froh, dass ich trotz der Experten, die mir halfen, immer noch nicht herausfinden konnte, warum die beiden Methoden nicht übereinstimmen. Ich werde nur die Methodik in der anderen Antwort verwenden, um zu extrapolieren. Ich wünschte, ich könnte für beide Antworten mit Ja stimmen, aber @srutzky half bei allen Gründen, warum die beiden ausfallen würden.
Chris Woods
6

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.

Paparazzo
quelle