Wie große Datenmengen am effizientesten löschen?
Dieser Artikel beschreibt Strategien, wie große Datenmengen aus Relationen gelöscht werden können und dabei möglichst effizient und schnell zu arbeiten. Ziel einer solchen Operation ist die Vermeidung großer Datenmengen im Transaktionsprotokoll. Diese Aufgabenstellung zu dokumentieren, rührt von den immer wieder auftretenden Fragen in den Microsoft Foren bezüglich effizienter Löschstrategien:
Need to build a job that purges old data and rebuilds indexes
Removing large number of records with truncate?
Problembeschreibung
Häufig kommt es in großen Datenbanksystemen zu Aufgabenstellungen, große Teilmengen der Daten und Altbestände oder sonstige Daten aus der Datenbank zu entfernen. Beim Löschen großer Datenmengen muss der Datenbankadministrator mehrere Aspekte berücksichtigen, die seine Entscheidung maßgeblich beeinflussen:
- Ist die Datenbank Bestandteil einer Hochverfügbarkeitslösung, die einen Wechsel des Wiederherstellungsmodells erschwert oder sogar unmöglich macht?
- Wie viele Indexe sind von der Löschaktion in einer Relation betroffen?
- Ist die betroffene Relation von anderen Objekten abhängig, die eine bevorzugte Löschoperation verhindern? - FOREIGN KEY – Einschränkung - Schemagebundene Views / Funktionen / ...
- Wie fragmentiert sind nach dem Löschen die Indexe, die für die Relation erstellt wurden? Muss eventuell nach dem Löschvorgang ein ALTER INDEX REBUILD durchgeführt werden?
Alle obigen Fragen müssen vom Datenbankadministrator berücksichtigt werden, um die richtige Strategie festzulegen. Die nachfolgenden Beispiele zeigen Möglichkeiten auf und zeigen auch die entsprechenden Einschränkungen, die mit der Lösung einhergehen!
Testumgebung
Als Testumgebung wird eine Datenbank angelegt, in der sich eine Testtabelle mit 100.000 Datensätzen befindet. Diese Relation besitzt keine weiteren Einschränkungen und soll nur das generierte Transaktionsvolumen messen, dass bei den jeweiligen Lösungsstrategien generiert wird.
1: -- Erstellen der Demonstrations-Datenbank
2: IF db_id('DeleteRecord') IS NOT NULL
3: BEGIN
4: ALTER DATABASE DeleteRecord SET SINGLE_USER WITH ROLLBACK IMMEDIATE;
5: DROP DATABASE DeleteRecord;
6: END
7:
8: CREATE DATABASE DeleteRecord
9: ON PRIMARY
10: (
11: NAME = N'DelRecord',
12: FILENAME = N'S:\Backup\DelRecord.mdf',
13: SIZE = 100MB,
14: MAXSIZE = 1000MB,
15: FILEGROWTH = 100MB
16: )
17: LOG ON
18: (
19: NAME = N'DelRecord_log',
20: FILENAME = N'S:\Backup\DelRecord.ldf',
21: SIZE = 100MB,
22: MAXSIZE = 1GB,
23: FILEGROWTH = 100MB
24: );
25: GO
26:
27: -- Das Wiederherstellungsmodell wird initial auf SIMPLE gestellt
28: ALTER DATABASE DeleteRecord SET RECOVERY SIMPLE;
29: GO
30:
31: -- Erstellen der Relation für die Testdaten
32: USE DeleteRecord;
33: GO
34:
35: CREATE TABLE dbo.bigtable
36: (
37: Id int NOT NULL IDENTITY (1, 1),
38: c1 char(100) NOT NULL DEFAULT ('only stupid stuff'),
39: c2 varchar(100) NOT NULL DEFAULT ('more stupid stuff'),
40: c3 date NOT NULL DEFAULT (getdate()),
41:
42: CONSTRAINT pk_bigTable PRIMARY KEY CLUSTERED (Id)
43: );
44: GO
45:
46: -- Eintragen von 100.000 Datensätzen
47: SET NOCOUNT ON
48: GO
49:
50: INSERT INTO dbo.bigtable DEFAULT VALUES
51: GO 100000
Die Relation [dbo].[bigtable] besitzt 100.000 Datensätze, von denen in den nachfolgenden Szenarien jeweils die Hälfte der Datensätze gelöscht werden sollen.
Löschen von Datensätzen mit DELETE FROM <table>
Die wohl bekannteste Möglichkeit zum Löschen von Datensätzen ist die DELETE-Operation. DELETE ist eine DML-Operation und hat entsprechende Einschränkungen. Eine DML-Operation ist immer eine vollständig protokollierte Operation – das heißt also, dass beim Löschen von großen Datenmengen JEDE zu löschende Datenzeile protokolliert wird.
Befindet sich die Datenbank in einem Hochverfügbarkeitsumfeld, kann ein solcher Löschvorgang für den Spiegel (Mirroring), Replica (AlwaysOn) oder das Backup (Log Shipping) problematisch werden. Eine vollständig protokollierte Transaktion dauert entsprechend lang, da jede Einzelaktion in das Protokoll geschrieben werden muss. Neben dem erzeugten Datenvolumen muss ebenfalls berücksichtigt werden, dass während des Löschvorgangs Sperren auf die Ressourcen gesetzt werden. Im Falle einer “Lock-Eskalation” kann dies (bei großer Datenmenge) zu einer vollständigen Tabellensperre (Table-Lock) führen (http://technet.microsoft.com/de-de/library/ms184286(v=sql.105).aspx).
Dem Datenbankadministrator muss also daran gelegen sein, neben einem schnellen Löschvorgang auch das generierte Transaktionsprotokoll so gering wie möglich zu halten. Sehr häufig wird daher in den Foren von zwei Strategien gesprochen, die sowohl das Transaktionsvolumen gering halten sollen als auch die Sperren auf die Ressourcen verringern.
Ändern des Wiederherstellungsmodells auf SIMPLE oder BULK_LOGGED
Dieser – sehr häufig in den Foren zu lesende – Vorschlag ist nicht praktikabel, da er auf DELETE als DML-Operation nicht anwendbar ist. DELETE ist eine DML-Operation, die eine vollständige Protokollierung unabhängig vom Wiederherstellungsmodell erzwingt. Das Beispiel zeigt das generierte Transaktionsvolumen, das bei unterschiedlichen Wiederherstellungsmodellen generiert wird. Dazu wird das folgende Script zum Löschen aller Datensätzen mit einer geraden Id angewendet:
1: USE DeleteRecord;
2: GO
3:
4: -- Bei Messung das entsprechende Wiederherstellungsmodell wählen
5: ALTER DATABASE DeleteRecord SET RECOVERY SIMPLE -- BULK_LOGGED, FULL;
6: GO
7:
8: -- Tabelle für die Speicherung des Transaktionsvolumens
9: DECLARE @ResultTable TABLE
10: (
11: Id int NOT NULL IDENTITY (1, 1),
12: Operation varchar(20) NOT NULL,
13: bytes bigint NOT NULL
14: );
15:
16: -- Beginn der Transaktion
17: BEGIN TRANSACTION
18: -- Löschen der Hälfte des Datenvolumens
19: DELETE dbo.bigTable WHERE Id % 2 = 0;
20:
21: INSERT INTO @ResultTable (Operation, Bytes)
22: SELECT 'DELETE', database_transaction_log_bytes_used
23: FROM sys.dm_tran_database_transactions
24: WHERE database_id = db_id();
25:
26: -- Neuerstellung aller (1) Index(e) wegen Fragmentierung
27: ALTER INDEX ALL ON dbo.bigTable REBUILD;
28:
29: INSERT INTO @ResultTable (Operation, Bytes)
30: SELECT 'REBUILD', database_transaction_log_bytes_used
31: FROM sys.dm_tran_database_transactions
32: WHERE database_id = db_id();
33:
34: - Ausgabe des gemessenen Transaktionsvolumens
35: SELECT Id,
36: Operation,
37: Bytes - LAG(Bytes, 1, 0) OVER (ORDER BY Id) AS TransactionVolume
38: FROM @ResultTable;
39: ROLLBACK TRANSACTION
Das Script stellt zunächst das gewünschte Wiederherstellungsmodell ein um anschließend den Löschvorgang durchzuführen. Dazu werden die generierten Transaktionsvolumina in einer Tabellenvariablen zwischengespeichert um sie später auswerten zu können. Für jedes Wiederherstellungsmodell wurde das Transaktionsvolumen gemessen – folgende Ergebnisse wurden dabei protokolliert:
Wie der Vergleich zeigt, ist das generierte Transaktionsvolumen für den eigentlichen Löschvorgang (DELETE) in allen Wiederherstellungsmodellen identisch – eben weil DELETE ein vollständig protokollierter DML-Befehl ist. Interessant ist jedoch, wie sich das Transaktionsvolumen bei der Neuerstellung des Index verändert. Dieser Unterschied rührt daher, dass die Protokollierung der Indexänderungen vom Wiederherstellungsmodell abhängt. ALTER INDEX REBUILD ist in den Wiederherstellungsmodellen SIMPLE und BULK_LOGGED nur “minimal logged” (http://technet.microsoft.com/en-us/library/ms191484(v=sql.105).aspx).
Problematisch ist bei der zu löschenden Datenmenge, welche Sperren Microsoft SQL Server verwendet, um den Löschvorgang abzuschließen. Da es sich um 50% der gesamten Datenmenge handelt, wendet Microsoft SQL Server eine Tabellensperre an – somit ist während des Löschvorgangs ein Zugriff auf die Relation nicht möglich!
Zwischenergebnis
Das Ändern des Wiederherstellungsmodells bringt keine Vorteile beim Löschen von Datensätzen mittels DELETE. Da DELETE eine vollständig protokollierte Operation ist, ändert sich das generierte Datenvolumen nicht. Wird zusätzlich ein INDEX REBUILD durchgeführt, verändert sich das Transaktionsvolumen im Wiederherstellungsmodus FULL signifikant, da das INDEX REBUILD in diesem Wiederherstellungsmodus die Erstellung vollständig protokolliert. Andere Wiederherstellungsmodelle verwenden “minimal logging” bei dieser Aktion.
Löschen von Datensätzen mit TRUNCATE
TRUNCATE ist – im Gegensatz zu DELETE – keine DML-Operation sondern eine DDL-Operation. Bei TRUNCATE werden nicht die Daten selbst geändert sondern das zu Grunde liegende Schema (es wird den Daten quasi “der Teppich unter den Füssen” entfernt!). Die detaillierte Funktionsweise mit allen Vor- und Nachteilen habe ich im Artikel “TRUNCATE <tablename> wird nicht protokolliert?” beschrieben.
Bei diesem Vorgehen werden Daten, die nicht gelöscht werden, zunächst in einer Stagingtabelle gespeichert. Anschließend wird die Relation mit TRUNCATE komplett neu aufgebaut und die Daten werden aus der Stagingtabelle in die neu erstellte Relation überführt. Auf Grund der Beschränkungen von TRUNCATE ist diese Operation nur möglich, wenn die Quelle keine Fremdschlüsselbeziehungen hat und/oder abhängig von schemagebundenen Objekten ist.
Da das besondere Augenmerk dieses Artikels auf dem generierten Transaktionsvolumen liegt, kann diese Einschränkung für das Beispiel außer acht gelassen werden. Das Script für das Löschen der Datensätze sieht wie folgt aus:
1: ALTER DATABASE DeleteRecord SET RECOVERY SIMPLE -- BULK_LOGGED, FULL;
2: GO
3:
4: DECLARE @ResultTable TABLE
5: (
6: Id int NOT NULL IDENTITY (1, 1),
7: Operation varchar(20) NOT NULL,
8: bytes bigint NOT NULL
9: );
10:
11: BEGIN TRANSACTION
12: -- Bulk logged
13: SELECT *
14: INTO dbo.bigtable_intermediate
15: FROM dbo.bigtable
16: WHERE Id % 2 = 0;
17:
18: INSERT INTO @ResultTable (Operation, Bytes)
19: SELECT 'MOVE', database_transaction_log_bytes_used
20: FROM sys.dm_tran_database_transactions
21: WHERE database_id = db_id();
22:
23: -- minimally logged because DDL-Operation
24: TRUNCATE TABLE dbo.bigtable;
25:
26: INSERT INTO @ResultTable (Operation, Bytes)
27: SELECT 'TRUNCATE', database_transaction_log_bytes_used
28: FROM sys.dm_tran_database_transactions
29: WHERE database_id = db_id();
30:
31: SET IDENTITY_INSERT dbo.bigTable ON;
32: INSERT INTO dbo.bigtable (Id, c1, c2, c3)
33: SELECT Id, c1, c2, c3 FROM dbo.bigtable_intermediate ORDER BY Id;
34: SET IDENTITY_INSERT dbo.bigtable OFF;
35:
36: INSERT INTO @ResultTable (Operation, Bytes)
37: SELECT 'TRANSFER BACK', database_transaction_log_bytes_used
38: FROM sys.dm_tran_database_transactions
39: WHERE database_id = db_id();
40:
41: SELECT Id,
42: Operation,
43: Bytes - LAG(Bytes, 1, 0) OVER (ORDER BY Id) AS TransactionVolume
44: FROM @ResultTable;
45: ROLLBACK TRANSACTION
Das obige Script überträgt zunächst die NICHT zu löschenden Daten in eine Zwischentabelle [dbo].[bigtable_intermediate]. Anschließend wird die Quelle mittels TRUNCATE komplett neu aufgebaut (es werden ausschließlich Metadaten geändert!). Nachdem die Relation neu aufgebaut wurde, können die zwischengespeicherten Daten wieder in die Quelltabelle übertragen werden.
Interessant bei diesem Verfahren ist, dass die Operation “SELECT ... INTO” bulk logged unterstützt. Das bedeutet für die Transaktion, dass die Operation relativ wenig Transaktionsvolumen generiert und der Prozessteil relativ schnell abgearbeitet ist.
Bei TRUNCATE handelt es sich um eine DDL-Operation – es werden also keine Daten als solche gelöscht sondern ausschließlich die den Daten zugrunde liegende Struktur. Dieser Teil der Transaktion sollte also auch kein großes Transaktionsvolumen generieren.
Die Operation “INSERT INTO ... SELECT ... FROM” besitzt ebenfalls die Fähigkeit, als bulk logged Operation verwendet zu werden (http://technet.microsoft.com/en-us/library/ms174335(v=sql.105).aspx). Das Ergebnis der Messungen für alle drei Wiederherstellungsmodelle sieht wie folgt aus:
Die Zahlen zeigen das für jeden Transaktionsschritt generierte Transaktionsvolumen. Auffällig ist erneut, dass im Wiederherstellungsmodell FULL für den Transfer der Daten in die Stagingtabelle fast 20x mehr Protokollvolumen generiert wird, als in den anderen beiden Wiederherstellungsmodellen.
Wie weiter oben beschrieben sollte auch “INSERT INTO” die Möglichkeit besitzen von den Vorteilen des BULK LOGGED zu partizipieren. Betrachtet man sich jedoch die Ergebnisse, wird schnell erkennbar, dass Microsoft SQL Server diese Option für den Transfer nicht verwendet hat.
Um die Operation INSERT INTO als BULK LOGGED Operation zu verwenden, müssen einige Voraussetzungen erfüllt sein:
- Das Wiederherstellungsmodell muss SIMPLE oder BULK_LOGGED sein
- Das Ziel muss leer sein oder aber es handelt sich um einen HEAP
- Das Ziel darf nicht Bestandteil einer Replikation sein
- Das Ziel muss exklusiv mit dem Hinweis TABLOCK gesperrt werden
Die drei ersten Optionen können vernachlässigt werden, da die Datenbank sich einem der geforderten Wiederherstellungsmodelle befunden hat. Auch war die Relation nach der Ausführung von TRUNCATE vollständig geleert und die Relation ist nicht Bestandteil einer Replikation. Das Zielobjekt wurde aber nicht explizit gesperrt. Ändert man das Script für den Bereich des Rücktransfers wie folgt ab, ist das Objekt gesperrt:
1: SET IDENTITY_INSERT dbo.bigTable ON;
2: INSERT INTO dbo.bigtable WITH (TABLOCK) (Id, c1, c2, c3)
3: SELECT Id, c1, c2, c3 FROM dbo.bigtable_intermediate ORDER BY Id;
4: SET IDENTITY_INSERT dbo.bigtable OFF;
Die neuen Messungen zeigen den Unterschied deutlich – auf eine Kommentierung verzichte ich:
Zwischenergebnis
Die Verwendung von TRUNCATE als Löschmechanismus ist die effektivste Lösung. Alle für eine solche Lösung verwendeten DML / DDL – Operationen werden minimal protokolliert oder weisen auf Grund ihrer Arbeitsweise kein hohes Transaktionsvolumen auf.
Zusammenfassung
Zwei Lösungen mit unterschiedlichem Ausgang. Jede Lösung hat ihre Berechtigung. Während DELETE zwar das schlechteste Verhalten in Bezug auf das Transaktionsvolumen besitzt, ist es die einzige Möglichkeit in Hochverfügbarkeitslösungen wie Spiegelung, AlwaysOn (Für Log Shipping wäre das BULK_LOGGED Wiederherstellungsmodell möglich!). Auch gilt es, zwischen “Performance” und RPO (Recovery Point Objectives) abzuwägen.
Wird das Wiederherstellungsmodell von FULL auf SIMPLE geändert, werden Transaktionen, die während des Löschvorgangs von anderen Benutzern ausgeführt werden, nicht mehr im Transaktionsprotokoll gespeichert und Änderungen können nicht mehr rückgängig gemacht werden.
Sind die Voraussetzungen für die Verwendung von TRUNCATE gegeben, sollte man diese Lösung wählen, wenn es “schnell” gehen soll. Insbesondere bietet sich diese Möglichkeit an, wenn ein geplantes Wartungsfenster zur Verfügung steht, in dem niemand Daten bearbeitet. Nachteilig sind die Einschränkungen, die dieser Lösung vorausgehen.
Herzlichen Dank fürs Lesen!
Print article | This entry was posted by Uwe Ricken on 29.10.13 at 17:29:00 . Follow any responses to this post through RSS 2.0. |