[ Teil 1 | Teil 2 | Teil 3 | Teil 4 ]
Ein Problem, das ich in letzter Zeit einige Male gesehen habe, ist das Szenario, in dem Sie eine IDENTITY-Spalte als INT erstellt haben und sich jetzt der Obergrenze nähern und sie vergrößern müssen (BIGINT). Wenn Ihre Tabelle groß genug ist, dass Sie die Obergrenze einer ganzen Zahl (über 2 Milliarden) erreichen, ist dies keine Operation, die Sie zwischen dem Mittagessen und Ihrer Kaffeepause an einem Dienstag durchführen können. In dieser Serie werden die Mechanismen hinter einer solchen Änderung und verschiedene Möglichkeiten untersucht, dies mit unterschiedlichen Auswirkungen auf die Betriebszeit zu erreichen. Im ersten Teil wollte ich mir die physikalischen Auswirkungen der Änderung einer INT in eine BIGINT ohne eine der anderen Variablen genau ansehen.
Was passiert eigentlich, wenn Sie einen INT erweitern?
INT und BIGINT sind Datentypen mit fester Größe, daher muss eine Konvertierung von einem zum anderen die Seite berühren, was dies zu einer Operation mit Datengröße macht. Dies ist kontraintuitiv, da es so aussieht, als wäre es nicht möglich, dass eine Änderung des Datentyps von INT zu BIGINT den zusätzlichen Platz auf der Seite sofort (und überhaupt für eine IDENTITY-Spalte) benötigt. Logischerweise ist dies Platz, der möglicherweise erst später benötigt wird, wenn ein bestehender INT-Wert auf einen Wert> 4 Bytes geändert wurde. Aber so funktioniert es heute nicht mehr. Lassen Sie uns eine einfache Tabelle erstellen und sehen:
CREATE TABLE dbo.FirstTest ( RowID int IDENTITY(1,1), Filler char(2500) NOT NULL DEFAULT 'x' ); GO INSERT dbo.FirstTest WITH (TABLOCKX) (Filler) SELECT TOP (20) 'x' FROM sys.all_columns AS c; GO
Eine einfache Abfrage kann mir die niedrigste und höchste Seite, die diesem Objekt zugeordnet ist, sowie die Gesamtzahl der Seiten mitteilen:
SELECT lo_page = MIN(allocated_page_page_id), hi_page = MAX(allocated_page_page_id), page_count = COUNT(*) FROM sys.dm_db_database_page_allocations ( DB_ID(), OBJECT_ID(N'dbo.FirstTest'), NULL, NULL, NULL );
Wenn ich nun diese Abfrage vor und nach dem Ändern des Datentyps von INT in BIGINT ausführe:
ALTER TABLE dbo.FirstTest ALTER COLUMN RowID bigint;
Ich sehe diese Ergebnisse:
-- before: lo_page hi_page page_count ------- ------- ---------- 243 303 17 -- after: lo_page hi_page page_count ------- ------- ---------- 243 319 33
Es ist klar, dass 16 neue Seiten hinzugefügt wurden, um Platz für den zusätzlich benötigten Platz zu schaffen (obwohl wir wissen, dass keiner der Werte in der Tabelle tatsächlich 8 Byte benötigt). Dies wurde jedoch nicht so erreicht, wie Sie vielleicht denken – anstatt die Spalte auf den vorhandenen Seiten zu erweitern, wurden die Zeilen auf neue Seiten verschoben, wobei an ihrer Stelle Zeiger zurückgelassen wurden. Betrachtet man vorher und nachher Seite 243 (mit der undokumentierten DBCC PAGE
):
-- ******** Page 243, before: ******** Slot 0 Offset 0x60 Length 12 Record Type = PRIMARY_RECORD Record Attributes = NULL_BITMAP Record Size = 12 Memory Dump @0x000000E34B9FA060 0000000000000000: 10000900 01000000 78020000 .. .....x... Slot 0 Column 1 Offset 0x4 Length 4 Length (physical) 4 RowID = 1 Slot 0 Column 2 Offset 0x8 Length 1 Length (physical) 1 filler = x -- ******** Page 243, after: ******** Slot 0 Offset 0x60 Length 9 Record Type = FORWARDING_STUB Record Attributes = Record Size = 9 Memory Dump @0x000000E34B9FA060 0000000000000000: 04280100 00010078 01 .(.....x. Forwarding to = file 1 page 296 slot 376
Wenn wir uns dann das Ziel des Zeigers ansehen, Seite 296, Steckplatz 376, sehen wir:
Slot 376 Offset 0x8ca Length 34 Record Type = FORWARDED_RECORD Record Attributes = NULL_BITMAP VARIABLE_COLUMNS Record Size = 34 Memory Dump @0x000000E33BBFA8CA 0000000000000000: 32001100 01000000 78010000 00000000 00030000 2.......x........... 0000000000000014: 01002280 0004f300 00000100 0000 .."...ó....... Forwarded from = file 1 page 243 slot 0 Slot 376 Column 67108865 Offset 0x4 Length 0 Length (physical) 4 DROPPED = NULL Slot 376 Column 2 Offset 0x8 Length 1 Length (physical) 1 filler = x Slot 376 Column 1 Offset 0x9 Length 8 Length (physical) 8 RowID = 1
Dies ist offensichtlich eine sehr störende Änderung der Tabellenstruktur. (Und eine interessante Nebenbeobachtung:Die physische Reihenfolge der Spalten, RowID und Filler, wurde auf der Seite umgedreht.) Reservierter Speicherplatz springt von 136 KB auf 264 KB, und die durchschnittliche Fragmentierung steigt leicht von 33,3 % auf 40 %. Dieser Speicherplatz wird nicht durch eine Neuerstellung, online oder nicht, oder eine Neuorganisation wiederhergestellt, und – wie wir gleich sehen werden – liegt das nicht daran, dass die Tabelle zu klein ist, um davon zu profitieren.
Hinweis:Dies gilt sogar für die neuesten Builds von SQL Server 2016 – während immer mehr Operationen wie diese in modernen Versionen zu Nur-Metadaten-Operationen verbessert wurden, wurde diese noch nicht behoben, obwohl dies eindeutig ist es könnte sein – wiederum besonders in dem Fall, wo die Spalte eine IDENTITY-Spalte ist, die per Definition nicht aktualisiert werden kann.
Die Ausführung der Operation mit der neuen ALTER COLUMN / ONLINE-Syntax, über die ich letztes Jahr gesprochen habe, ergibt einige Unterschiede:
-- drop / re-create here ALTER TABLE dbo.FirstTest ALTER COLUMN RowID bigint WITH (ONLINE = ON);
Jetzt wird das Vorher und Nachher zu:
-- before: lo_page hi_page page_count ------- ------- ---------- 243 303 17 -- after: lo_page hi_page page_count ------- ------- ---------- 307 351 17
In diesem Fall handelte es sich immer noch um eine Datengrößenoperation, aber die vorhandenen Seiten wurden aufgrund der ONLINE-Option kopiert und neu erstellt. Sie fragen sich vielleicht, warum, wenn wir die Spaltengröße als ONLINE-Vorgang geändert haben, die Tabelle in der Lage ist, mehr Daten auf die gleiche Anzahl von Seiten zu stopfen? Jede Seite ist jetzt dichter (weniger Zeilen, aber mehr Daten pro Seite), auf Kosten der Streuung – die Fragmentierung verdoppelt sich von 33,3 % auf 66,7 %. Verwendeter Speicherplatz zeigt mehr Daten im selben reservierten Speicherplatz (von 72 KB / 136 KB bis 96 KB / 136 KB).
Und in größerem Maßstab?
Lassen Sie uns die Tabelle löschen, neu erstellen und mit viel mehr Daten füllen:
CREATE TABLE dbo.FirstTest ( RowID INT IDENTITY(1,1), filler CHAR(1) NOT NULL DEFAULT 'x' ); GO INSERT dbo.FirstTest WITH (TABLOCKX) (filler) SELECT TOP (5000000) 'x' FROM sys.all_columns AS c1 CROSS JOIN sys.all_columns AS c2;
Von Anfang an haben wir jetzt 8.657 Seiten, einen Fragmentierungsgrad von 0,09 % und einen belegten Speicherplatz von 69.208 KB / 69.256 KB.
Wenn wir den Datentyp in Bigint ändern, springen wir auf 25.630 Seiten, die Fragmentierung wird auf 0,06 % reduziert und der verwendete Speicherplatz beträgt 205.032 KB / 205.064 KB. Ein Online-Rebuild ändert nichts, ein Reorg auch nicht. Der gesamte Prozess, einschließlich eines Rebuilds, dauert auf meinem Rechner etwa 97 Sekunden (das Auffüllen der Daten hat ganze 2 Sekunden gedauert).
Wenn wir den Datentyp mit ONLINE in Bigint ändern, beträgt der Stoß nur 11.140 Seiten, die Fragmentierung beträgt 85,5 % und der verwendete Speicherplatz beträgt 89.088 KB / 89160 KB. Online-Rebuilds und -Reorgs ändern immer noch nichts. Diesmal dauert der gesamte Vorgang nur etwa eine Minute. Die neue Syntax führt also definitiv zu schnelleren Operationen und weniger zusätzlichem Speicherplatz, aber zu hoher Fragmentierung. Ich nehme es.
Als Nächstes
Ich bin mir sicher, dass Sie sich meine Tests oben ansehen und sich über ein paar Dinge wundern. Vor allem, warum ist der Tisch ein Haufen? Ich wollte untersuchen, was tatsächlich mit der Seitenstruktur und der Seitenanzahl passiert, ohne Indizes, Schlüssel oder Einschränkungen, die die Details verschleiern. Sie fragen sich vielleicht auch, warum diese Änderung so einfach war – in einem Szenario, in dem Sie eine echte IDENTITY-Spalte ändern müssen, ist dies wahrscheinlich auch der gruppierte Primärschlüssel und hat Fremdschlüsselabhängigkeiten in anderen Tabellen. Dies führt definitiv zu einigen Schluckauf in den Prozess. Wir werden uns diese Dinge im nächsten Beitrag der Serie genauer ansehen.
—
[ Teil 1 | Teil 2 | Teil 3 | Teil 4 ]