Ich habe eine Tabelle in einer Produktionsdatenbank mit einer Größe von 525 GB, von denen 383 GB nicht verwendet werden:
Ich möchte einen Teil dieses Speicherplatzes zurückfordern, aber bevor ich mich mit der Produktionsdatenbank beschäftige, teste ich einige Strategien für eine identische Tabelle in einer Testdatenbank mit weniger Daten. Diese Tabelle hat ein ähnliches Problem:
Einige Informationen zur Tabelle:
- Der Füllfaktor wird auf 0 gesetzt
- Es gibt ungefähr 30 Spalten
- Eine der Spalten ist ein LOB vom Typ Image, in dem Dateien mit einer Größe von wenigen KB bis zu mehreren hundert MB gespeichert werden
- Der Tabelle sind keine hypothetischen Indizes zugeordnet
Auf dem Server wird SQL Server 2017 (RTM-GDR) (KB4505224) - 14.0.2027.2 (X64) ausgeführt. Die Datenbank verwendet das SIMPLE
Wiederherstellungsmodell.
Einige Dinge, die ich ausprobiert habe:
- Wiederaufbau der Indizes:
ALTER INDEX ALL ON dbo.MyTable REBUILD
. Dies hatte vernachlässigbare Auswirkungen. - Umordnung der Indizes:
ALTER INDEX ALL ON dbo.MyTable REORGANIZE WITH(LOB_COMPACTION = ON)
. Dies hatte vernachlässigbare Auswirkungen. Kopierte die LOB-Spalte in eine andere Tabelle, löschte die Spalte, erstellte die Spalte neu und kopierte die Daten zurück (wie in diesem Beitrag erläutert: Freigeben von nicht verwendetem Speicherplatz in der SQL Server-Tabelle ). Dadurch wurde der ungenutzte Speicherplatz verkleinert, aber scheinbar nur in belegten Speicherplatz umgewandelt:
Verwenden Sie das Dienstprogramm bcp, um die Tabelle zu exportieren, zu kürzen und neu zu laden (wie in diesem Beitrag erläutert: Freigeben des nicht verwendeten Speicherplatzes für eine Tabelle ). Dadurch wurde auch der nicht genutzte Speicherplatz reduziert und der genutzte Speicherplatz in einem ähnlichen Ausmaß wie im obigen Bild vergrößert.
- Obwohl dies nicht empfohlen wird, habe ich die Befehle DBCC SHRINKFILE und DBCC SHRINKDATABASE ausprobiert, aber sie hatten keine Auswirkungen auf den nicht verwendeten Speicherplatz.
- Laufen
DBCC CLEANTABLE('myDB', 'dbo.myTable')
machte keinen Unterschied - Ich habe all das versucht, während die Bild- und Textdatentypen beibehalten wurden und nachdem die Datentypen in varbinary (max) und varchar (max) geändert wurden.
- Ich habe versucht, die Daten in eine neue Tabelle in einer neuen Datenbank zu importieren. Dabei wurde auch nur der nicht verwendete Speicherplatz in den verwendeten Speicherplatz konvertiert. Ich habe die Details dieses Versuchs in diesem Beitrag beschrieben .
Ich möchte diese Versuche nicht in der Produktionsdatenbank durchführen, wenn dies die erwarteten Ergebnisse sind.
- Warum wird der ungenutzte Speicherplatz nach einigen dieser Versuche nur in benutzten Speicherplatz konvertiert? Ich habe das Gefühl, ich habe kein gutes Verständnis dafür, was unter der Haube passiert.
- Kann ich noch etwas tun, um den nicht genutzten Speicherplatz zu verringern, ohne den genutzten Speicherplatz zu vergrößern?
BEARBEITEN: Hier ist der Datenträgernutzungsbericht und das Skript für die Tabelle:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[MyTable](
[Column1] [int] NOT NULL,
[Column2] [int] NOT NULL,
[Column3] [int] NOT NULL,
[Column4] [bit] NOT NULL,
[Column5] [tinyint] NOT NULL,
[Column6] [datetime] NULL,
[Column7] [int] NOT NULL,
[Column8] [varchar](100) NULL,
[Column9] [varchar](256) NULL,
[Column10] [int] NULL,
[Column11] [image] NULL,
[Column12] [text] NULL,
[Column13] [varchar](100) NULL,
[Column14] [varchar](6) NULL,
[Column15] [int] NOT NULL,
[Column16] [bit] NOT NULL,
[Column17] [datetime] NULL,
[Column18] [varchar](50) NULL,
[Column19] [varchar](50) NULL,
[Column20] [varchar](60) NULL,
[Column21] [varchar](20) NULL,
[Column22] [varchar](120) NULL,
[Column23] [varchar](4) NULL,
[Column24] [varchar](75) NULL,
[Column25] [char](1) NULL,
[Column26] [varchar](50) NULL,
[Column27] [varchar](128) NULL,
[Column28] [varchar](50) NULL,
[Column29] [int] NULL,
[Column30] [text] NULL,
CONSTRAINT [PK] PRIMARY KEY CLUSTERED
(
[Column1] ASC,
[Column2] ASC,
[Column3] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
ALTER TABLE [dbo].[MyTable] ADD CONSTRAINT [DF_Column4] DEFAULT (0) FOR [Column4]
GO
ALTER TABLE [dbo].[MyTable] ADD CONSTRAINT [DF_Column5] DEFAULT (0) FOR [Column5]
GO
ALTER TABLE [dbo].[MyTable] ADD CONSTRAINT [DF_Column15] DEFAULT (0) FOR [Column15]
GO
ALTER TABLE [dbo].[MyTable] ADD CONSTRAINT [DF_Column16] DEFAULT (0) FOR [Column16]
GO
Hier sind die Ergebnisse der Ausführung der Befehle in Max Vernons Antwort:
╔════════════╦═══════════╦════════════╦═════════════════╦══════════════════════╦════════════════════╗
║ TotalBytes ║ FreeBytes ║ TotalPages ║ TotalEmptyPages ║ PageBytesFreePercent ║ UnusedPagesPercent ║
╠════════════╬═══════════╬════════════╬═════════════════╬══════════════════════╬════════════════════╣
║ 9014280192║ 8653594624║ 1100376║ 997178 ║ 95.998700 ║ 90.621500 ║
╚════════════╩═══════════╩════════════╩═════════════════╩══════════════════════╩════════════════════╝
╔═════════════╦═══════════════════╦════════════════════╗
║ ObjectName ║ ReservedPageCount ║ UsedPageCount ║
╠═════════════╬═══════════════════╬════════════════════╣
║ dbo.MyTable ║ 5109090 ║ 2850245 ║
╚═════════════╩═══════════════════╩════════════════════╝
AKTUALISIEREN:
Ich habe folgendes ausgeführt, wie von Max Vernon vorgeschlagen:
DBCC UPDATEUSAGE (N'<database_name>', N'<table_name>');
Und hier war die Ausgabe:
DBCC UPDATEUSAGE: Usage counts updated for table 'MyTable' (index 'PK_MyTable', partition 1):
USED pages (LOB Data): changed from (568025) to (1019641) pages.
RSVD pages (LOB Data): changed from (1019761) to (1019763) pages.
Dadurch wurde die Datenträgerverwendung für die Tabelle aktualisiert:
Und die Gesamtbelegung der Festplatte:
Es sieht also so aus, als ob das Problem darin bestand, dass die Datenträgernutzung, die von SQL Server verfolgt wurde, nicht mehr mit der tatsächlichen Datenträgernutzung synchronisiert war. Ich werde dieses Problem als gelöst betrachten, aber ich würde gerne wissen, warum dies überhaupt passiert wäre!
DBCC CHECKDB
? Haben Sie darüber nachgedacht , von den sich bewegenden entfernt veraltete Datentypen ,text
undimage
? Sie könnten zu den falschen Statistiken beitragen.Antworten:
Als ersten Schritt würde ich DBCC UPDATEUSAGE für die Tabelle ausführen , da die Symptome eine inkonsistente Speichernutzung anzeigen.
Die Syntax lautet:
DBCC UPDATEUSAGE (N'<database_name>', N'<table_name>');
Nachdem Sie das ausgeführt haben, würde ich
EXEC sys.sp_spaceused
gegen den Tisch laufen :Der obige Befehl bietet die Möglichkeit, die Verwendung zu aktualisieren. Da Sie ihn jedoch
DBCC UPDATEUSAGE
zuerst manuell ausgeführt haben, belassen Sie diesen Wert einfach auf false. Ausführen vonDBCC UPDATEUSAGE
Hand können Sie sehen , ob etwas korrigiert.Die folgende Abfrage sollte den Prozentsatz der freien Bytes in der Tabelle und den Prozentsatz der freien Seiten in der Tabelle anzeigen. Da die Abfrage eine undokumentierte Funktion verwendet, ist es nicht ratsam, sich auf die Ergebnisse zu verlassen, sie erscheint jedoch im Vergleich zur Ausgabe von
sys.sp_spaceused
auf hoher Ebene genau .Wenn der Prozentsatz der freien Bytes deutlich höher ist als der Prozentsatz der freien Seiten, haben Sie viele teilweise leere Seiten.
Teilweise leere Seiten können verschiedene Ursachen haben:
Seitenteile, bei denen die Seite geteilt werden muss, um neue Einfügungen in den gruppierten Index aufzunehmen
Die Seite kann aufgrund der Spaltengröße nicht mit Spalten gefüllt werden.
Die Abfrage verwendet die undokumentierte
sys.dm_db_database_page_allocations
dynamische Verwaltungsfunktion:Die Ausgabe sieht folgendermaßen aus:
Ich habe einen Blog-Beitrag geschrieben, der die Funktion hier beschreibt .
In Ihrem Szenario sollten Sie, seit Sie ausgeführt haben
ALTER TABLE ... REBUILD
, eine sehr niedrige Zahl für sehenTotalEmptyPages
, aber ich schätze, Sie werden immer noch rund 72% in habenBytesFreePercent
.Ich habe Ihr
CREATE TABLE
Skript verwendet, um zu versuchen, Ihr Szenario neu zu erstellen.Dies ist das MCVE, das ich verwende:
Die folgende Abfrage zeigt eine einzelne Zeile für jede der Tabelle zugewiesene Seite und verwendet dieselbe undokumentierte DMV:
In der Ausgabe werden viele Zeilen angezeigt, wenn Sie sie in Ihrer Testumgebung für Ihre reale Tabelle ausführen, aber Sie können möglicherweise erkennen, wo das Problem liegt.
Können Sie das folgende Skript ausführen und die Ergebnisse in Ihrer Frage veröffentlichen? Ich versuche nur sicherzustellen, dass wir auf der gleichen Seite sind.
quelle
DBCC UPDATEUSAGE
wurden der nicht verwendete Speicherplatz und die nicht verwendete Seitenzahl aktualisiert. Es sieht so aus, als ob die Datenträgernutzung und die von SQL Server gemeldeten Seiteninformationen extrem nicht synchron waren. Ich habe meinen Beitrag mit den Details aktualisiert. Ich bin gespannt, wie das überhaupt passiert wäre, aber zumindest wurde das Problem gefunden. Vielen Dank für all Ihre Hilfe, ich weiß das sehr zu schätzen!Möglicherweise tritt eine interne Fragmentierung auf.
Was ist die Seitenfragmentierung für diese Tabelle?
Und unterscheidet sich die Fragmentierung für die In-Row von den Off-Row-Seiten?
Sie sagen, Sie haben Dateien, die ein paar KB groß sind.
SQL Server speichert alles in 8060-Byte-Seiten. Das heißt, wenn Sie eine Zeile (oder Off-Row-Daten) mit 4040 Bytes haben und die nächste ähnlich ist, können nicht beide auf dieselbe Seite passen, und Sie werden die Hälfte Ihres Speicherplatzes verschwenden. Versuchen Sie, die Zeilengröße zu ändern, indem Sie Spalten mit variabler Länge (beispielsweise mit dem Bild) in einer anderen Tabelle speichern.
quelle
Befindet sich die Datenbank im vollständigen Wiederherstellungsmodus? Wenn dies der Fall ist, werden beim Ausführen einer Verkleinerung alle Änderungen protokolliert und nicht so verkleinert, wie Sie es erwarten. Abhängig von Ihren Betriebsstunden können Sie eine Sicherungskopie erstellen, in den Massenversand-Wiederherstellungsmodus wechseln und dann die Verkleinerung der Datendatei ausführen. Danach möchten Sie Index-Skripte ausführen, um zu reparieren / neu zu erstellen und zur vollständigen Wiederherstellung zurückzukehren. Das würde ich auf jeden Fall versuchen, aber es hängt auch hier von Ihren Betriebsstunden ab.
quelle
Das einzige Mal, dass ich eine DB nicht verkleinern und Speicherplatz zurückfordern konnte, ist, dass Sie eine DB nicht über die ursprüngliche Größe der DB, als sie erstellt wurde, hinaus verkleinern können. Wenn Ihre DB beispielsweise eine Kopie der Produktions-DB ist und Sie die DB zum ersten Mal mit 525 GB erstellt haben, können Sie mit SQL Server die Größe nicht unter 525 GB verringern, unabhängig davon, wie viele Daten Sie aus der DB löschen. Wenn die Datenbank jedoch mit weniger als 383 GB erstellt wurde und dann 525 GB umfasst, sollten Sie keine Probleme haben, den Speicherplatz zurückzugewinnen. Ich habe lange gedacht, dass dies eine dumme und willkürliche Einschränkung von Microsoft ist.
Verkleinern Sie die Datenbank nur auf die ursprüngliche Größe, die nach dem Erstellen der Datenbank festgelegt wurde
quelle
Ich bin auf dieses Problem bei Produktionsboxen gestoßen. Sie müssen lediglich Tabellen und Indizes für jede Tabelle neu erstellen (in dieser Reihenfolge).
Hier ist die Abfrage, mit der ich Tabellen in Schach halte. Auf diese Weise können Sie ermitteln, welche Tabellen neu erstellt werden müssen, und die SQL-Abfragen erstellen, die Sie ausführen müssen. Diese Abfrage ist auf Abfragen mit mehr als 1 MB ungenutztem Speicherplatz und einer ungenutzten Quote von 5% beschränkt, sodass Sie nur das neu erstellen, worauf Sie sich wirklich konzentrieren müssen:
quelle