Schnellster Weg, um eine Spalte zu löschen oder zu löschen

7

Ich habe eine einzigartige Situation, in der ich eine Spalte schnell anonymisieren muss. Damit meine ich jede Möglichkeit, die Daten zu entfernen, egal ob es sich um NULL, Blanking oder etwas anderes handelt. Es können bis zu 20 Millionen Datensätze aktualisiert werden, und es gibt keine Indizes für die Spalte, die ich aktualisiere.

Ich habe ein paar Dinge ausprobiert, zum Beispiel:

Update TABLE Set COLUMN = NULL

Dies ist eindeutig die Abfrage mit der schlechtesten Leistung. Ich habe es geändert, um aktuelle NULL-Werte oder Leerzeichen aus diesem Satz auszuschließen, aber es ist immer noch sehr langsam.

Ich habe versucht, die Spalte zu löschen und neu zu erstellen, was bisher augenblicklich ist. Aber leider ist aus geschäftlichen Gründen die Spaltenreihenfolge wichtig, so dass dies ruiniert. (Für die Frage wird angenommen, dass eine Neuanordnung der Spalten nicht möglich ist.)

Kürzlich habe ich versucht, den Spaltentyp in char(1)und dann wieder in zu ändern text- was eine bessere Leistung hatte. Nachdem ich jedoch gesehen habe, wie es blitzschnell mit einem Drop ausgeführt und eine Spalte neu erstellt wurde, bin ich gespannt, ob es eine Möglichkeit gibt, dies zu tun, indem die Spaltenreihenfolge intakt bleibt. Offensichtlich ist SQL Server in der Lage, sofort ~ 20 Millionen Datensätze als NULL-Werte zu erstellen - muss es einen cleveren Weg geben, dies zu umgehen?

Kevin Pione
quelle

Antworten:

8

Lassen Sie uns zunächst einen kurzen Überblick darüber geben, wie SQL Server bei einer SELECT *Abfrage die Standardreihenfolge von Spalten generiert . Es gibt wahrscheinlich einige Randfälle, aber ich glaube, dass Spalten in der Reihenfolge zurückgegeben werden, in der sie erstellt wurden. Wenn eine Spalte gelöscht wird, wird die dieser Spalte zugeordnete Ordnungs-ID nicht verwendet und kann nicht von einer neuen Spalte verwendet werden.

Betrachten Sie diese Beispieltabelle:

CREATE TABLE dbo.SEE_COLUMN_ORDER (
COL1 INT,
COL2 INT,
COL3 INT,
COL4 INT
);

Wir können die column_id sehen, die diese Abfrage für sys.columns verwendet :

SELECT name, column_id
FROM sys.columns
WHERE object_id = OBJECT_ID('SEE_COLUMN_ORDER');

Erste Ergebnisse:

╔══════╦═══════════╗
 name  column_id 
╠══════╬═══════════╣
 COL1          1 
 COL2          2 
 COL3          3 
 COL4          4 
╚══════╩═══════════╝

Lassen Sie nun eine Spalte fallen:

ALTER TABLE dbo.SEE_COLUMN_ORDER DROP COLUMN COL3;

Neue Ergebnisse:

╔══════╦═══════════╗
 name  column_id 
╠══════╬═══════════╣
 COL1          1 
 COL2          2 
 COL4          4 
╚══════╩═══════════╝

Fügen Sie nun eine Spalte hinzu:

ALTER TABLE dbo.SEE_COLUMN_ORDER ADD COL3 INT;

Neue Ergebnisse:

╔══════╦═══════════╗
 name  column_id 
╠══════╬═══════════╣
 COL1          1 
 COL2          2 
 COL4          4 
 COL3          5 
╚══════╩═══════════╝

Es gibt keine Spalte mit einer column_idvon 3. Soweit ich das beurteilen kann, gibt es keine bekannte oder unterstützte Methode, COL3eine column_idvon 3 zu erstellen. Wenn Sie möchten, dass diese Spalte in einer SELECT *Abfrage an dritter Stelle angezeigt wird, können Sie eine Ansicht für die Tabelle mit definieren den Namen und ein anderes Schema oder Sie können die gesamte Tabelle mit der gewünschten Spaltenreihenfolge löschen und neu erstellen.

Das Wiederherstellen einer gesamten Tabelle klingt nach einem langsamen Vorgang, kann jedoch manchmal schneller sein als das Aktualisieren aller Zeilen für eine einzelne Spalte. Dies hängt von Ihrem System, Ihrer Tabellenstruktur und den Daten in Ihrer Tabelle ab. Ein Szenario, in dem das Löschen und Erstellen möglicherweise schneller ist, besteht darin, dass das Schreiben Ihres Transaktionsprotokolls den Engpass darstellt. Mit einem einfachen Wiederherstellungsmodell können Sie die neue Tabelle mit minimaler Protokollierung erstellen, die im Vergleich zu nur sehr wenige Daten in das Transaktionsprotokoll schreibt UPDATE. Ein weiteres Szenario, in dem das Löschen und Wiederherstellen schneller erfolgen kann, besteht darin, dass UPDATEdies zu vielen Seitenteilen führt. Es ist möglich, eine Tabelle und eine zu UPDATEerstellen, die jede Datenseite in zwei Teile aufteilt (einen UPDATEin einen Spaltenwert, der sie ergibtNULLwerde das soweit ich weiß nicht tun). Als Faustregel gilt, dass unabhängig vom Wiederherstellungsmodell dieselbe Datenmenge im Protokoll gespeichert wird, es sei denn, der Vorgang kann nur minimal protokolliert werden. UPDATEwird nie minimal protokolliert, so dass ein Wechsel zu einfach die Transaktionsprotokollanforderungen für diesen Vorgang nicht verringert.

Um den Teil der Frage zu behandeln, der sich mit der Leistung befasst, ist es wichtig zu beachten, dass das Hinzufügen und Löschen einer Spalte zu einer vorhandenen Tabelle optimierte Vorgänge sind, deren Fixkosten nicht mit der Datenmenge in der Tabelle skaliert werden. Um dies in Aktion zu sehen, werde ich untersuchen, wie viele Daten im Transaktionsprotokoll für die Vorgänge protokolliert werden (das Löschen und Hinzufügen von Spalten kann in einer Transaktion zurückgesetzt werden). Ich teste gegen SQL Server 2016.

Hier sind die Beispieldaten:

DROP TABLE IF EXISTS dbo.X_COLUMN_WIPE_2;
CREATE TABLE dbo.X_COLUMN_WIPE_2 (
    ID INT NOT NULL IDENTITY (1, 1),
    COL_TO_WIPE VARCHAR(1) NULL,
    FILLER VARCHAR(100) NULL,
    PRIMARY KEY (ID)
);

-- 2536 rows
INSERT INTO dbo.X_COLUMN_WIPE_2 WITH (TABLOCK)
SELECT 'A', REPLICATE('Z', 100)
FROM master..spt_values t1;

Per sys.dm_tran_database_transactions , das UPDATEschreibt 324752 log Bytes in das Transaktionsprotokoll:

BEGIN TRANSACTION

UPDATE dbo.X_COLUMN_WIPE_2 SET COL_TO_WIPE = NULL; 

ROLLBACK;

Durch das Löschen und Hinzufügen einer Spalte werden nur 1992 Protokollbytes in das Protokoll geschrieben:

BEGIN TRANSACTION

ALTER TABLE dbo.X_COLUMN_WIPE_2 DROP COLUMN COL_TO_WIPE;

ALTER TABLE dbo.X_COLUMN_WIPE_2 ADD NEW_COLUMN VARCHAR(1) NULL;

ROLLBACK;

Jetzt mit mehr Daten testen:

DROP TABLE IF EXISTS dbo.X_COLUMN_WIPE_3;

CREATE TABLE dbo.X_COLUMN_WIPE_3 (
    ID INT NOT NULL IDENTITY (1, 1),
    COL_TO_WIPE VARCHAR(1) NULL,
    FILLER VARCHAR(100) NULL,
    PRIMARY KEY (ID)
);

-- 6431296 rows
INSERT INTO dbo.X_COLUMN_WIPE_3 WITH (TABLOCK)
SELECT 'A', REPLICATE('Z', 100)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

Das gleiche UPDATEschreibt jetzt 721979808 Bytes in das Transaktionsprotokoll, aber das Löschen und Erstellen einer Spalte schreibt immer noch nur 1992 Bytes.

Mit der undokumentierten DBCC-SEITE können Sie die Interna untersuchen, warum dies geschieht . Hier ist ein Beispiel aus der Zeit, als ich den Code auf meinem System ausgeführt habe (das Kopieren und Einfügen des Codes funktioniert nicht, da die Seitenzahlen unterschiedlich sind):

DROP TABLE IF EXISTS dbo.X_COLUMN_WIPE_4;
CREATE TABLE dbo.X_COLUMN_WIPE_4 (
    ID INT NOT NULL IDENTITY (1, 1),
    COL_TO_WIPE VARCHAR(1) NULL,
    FILLER VARCHAR(100) NULL,
    PRIMARY KEY (ID)
);

INSERT INTO dbo.X_COLUMN_WIPE_4 WITH (TABLOCK)
SELECT 'A', REPLICATE('Z', 100)
FROM master..spt_values t1;

-- first first data page
DBCC IND('SE_DB',X_COLUMN_WIPE_4,-1)

-- view first data page
DBCC TRACEON(3604)
DBCC PAGE('SE_DB',1,1192232,3);

BEGIN TRANSACTION

UPDATE dbo.X_COLUMN_WIPE_4 SET COL_TO_WIPE = NULL; 

DBCC PAGE('SE_DB',1,1192232,3);

ROLLBACK;

Wie Paul White sagen würde, bin ich ziemlich der Amateur, wenn es um dieses Zeug geht, aber ich werde trotzdem meine Interpretation geben. Hier ist ein Unterschied zu den Angaben auf der Seite für den ersten Datensatz in der Tabelle vor und nach dem Update:

Seite nach dem Update

Ich habe unterstrichen, was ich vermute, sind die wichtigen Seiten in Rot. Beachten Sie, dass sich viele der physischen Längen geändert haben und der Wert "A" für COL_TO_WIPEin den Zeilendaten nicht mehr vorhanden ist. Es sieht so aus, als hätte UPDATEsich ein Großteil der auf der Seite gespeicherten Daten geändert.

Hier ist ein Unterschied zwischen der ursprünglichen Tabelle und dem COL_TO_WIPELöschen der Spalte:

nach dem Tropfen

Die Anzahl der Unterschiede ist viel geringer als zuvor. Keine der Zeilendaten wird geändert. Ich glaube, dass nur eine Metadatenoperation für die Tabelle außerhalb der Tabellenseiten ausgeführt wird, sodass alle DBCC PAGEhier angezeigten Änderungen logisch und nicht physisch sind.

Hier ist ein Unterschied zwischen der ursprünglichen Tabelle und nachdem die Spalte gelöscht und eine neue Spalte hinzugefügt wurde:

Spalte hinzufügen

Nach wie vor scheint es keine physischen Unterschiede zu geben. Die tatsächlichen Daten scheinen genau gleich zu sein. Es gibt nur Spaltendefinitionen mit einer physischen Länge von 0.

Da das Problem hier damit zu tun zu haben scheint, wie das Datum Zeile für Zeile auf Seiten gespeichert wird, sind die Ergebnisse möglicherweise für Tabellen unterschiedlich, die als gruppierte Spaltenspeicherindizes erstellt wurden, die im Spaltenformat gespeichert sind. Es ist schließlich vernünftig zu glauben, dass das Aktualisieren eines Spaltenwerts nur die eine Spalte anstelle aller Daten in der Tabelle betreffen sollte. Leider ist für diesen Fall die aktuelle Implementierung von UPDATEgegen CCIs ein logisches Löschen und Einfügen. Wenn Sie also alle Zeilen für eine Spalte aktualisieren NULL, markiert SQL Server alle vorhandenen Zeilen in der Tabelle als logisch gelöscht und erstellt neue Zeilengruppen für alle Zeilen.

Zusammenfassend lässt sich sagen, dass Sie, abgesehen von der Neuerstellung der gesamten Tabelle, bereits alle "cleveren" Methoden in der Frage erwähnt haben, die ich kenne, um Ihr Problem zu umgehen. Es ist sehr unwahrscheinlich, dass es eine "sofortige" Möglichkeit gibt, eine Spalte so zu aktualisieren, dass sie sich NULLin der Mitte der Tabelle befindet, ohne die vorherige Spaltenreihenfolge in der Tabelle zu unterbrechen.

Joe Obbish
quelle