Wenn ich die Anforderung richtig verstehe, besteht das Ziel darin, Stapel von Zeilen zu löschen, während gleichzeitig DML-Vorgänge für Zeilen in der gesamten Tabelle ausgeführt werden. Das Ziel ist das Löschen eines Stapels. Wenn jedoch zugrunde liegende Zeilen in dem von diesem Stapel definierten Bereich gesperrt sind, müssen wir diesen Stapel überspringen und zum nächsten Stapel übergehen. Wir müssen dann zu allen Stapeln zurückkehren, die zuvor nicht gelöscht wurden, und unsere ursprüngliche Löschlogik wiederholen. Wir müssen diesen Zyklus wiederholen, bis alle erforderlichen Zeilenstapel gelöscht sind.
Wie bereits erwähnt, ist es sinnvoll, einen READPAST-Hinweis und die Isolationsstufe READ COMMITTED (Standard) zu verwenden, um vergangene Bereiche zu überspringen, die möglicherweise blockierte Zeilen enthalten. Ich werde noch einen Schritt weiter gehen und empfehlen, die Isolationsstufe SERIALISIERBAR zu verwenden und Löschvorgänge zu knabbern.
SQL Server verwendet Schlüsselbereichssperren, um einen Bereich von Zeilen zu schützen, die implizit in einem Datensatz enthalten sind, der von einer Transact-SQL-Anweisung gelesen wird, während die serialisierbare Transaktionsisolationsstufe verwendet wird. Weitere Informationen finden Sie hier:
https://technet.microsoft.com /en-US/library/ms191272(v=SQL.105).aspx
Beim Löschen von Nibbeln ist es unser Ziel, einen Bereich von Zeilen zu isolieren und sicherzustellen, dass keine Änderungen an diesen Zeilen vorgenommen werden, während sie gelöscht werden. Das heißt, wir möchten keine Phantom-Lesevorgänge oder Einfügungen. Die serialisierbare Isolationsstufe soll dieses Problem lösen.
Bevor ich meine Lösung demonstriere, möchte ich hinzufügen, dass ich weder empfehle, die Standardisolationsstufe Ihrer Datenbank auf SERIALIZABLE zu ändern, noch empfehle ich, dass meine Lösung die beste ist. Ich möchte es nur vorstellen und sehen, wo wir von hier aus hingehen können.
Ein paar haushaltsnotizen:
- Die von mir verwendete SQL Server-Version ist Microsoft SQL Server 2012 - 11.0.5343.0 (X64).
- Meine Testdatenbank verwendet das vollständige Wiederherstellungsmodell
Zu Beginn meines Experiments werde ich eine Testdatenbank und eine Beispieltabelle einrichten und die Tabelle mit 2.000.000 Zeilen füllen.
USE [master];
GO
SET NOCOUNT ON;
IF DATABASEPROPERTYEX (N'test', N'Version') > 0
BEGIN
ALTER DATABASE [test] SET SINGLE_USER
WITH ROLLBACK IMMEDIATE;
DROP DATABASE [test];
END
GO
-- Create the test database
CREATE DATABASE [test];
GO
-- Set the recovery model to FULL
ALTER DATABASE [test] SET RECOVERY FULL;
-- Create a FULL database backup
-- in order to ensure we are in fact using
-- the FULL recovery model
-- I pipe it to dev null for simplicity
BACKUP DATABASE [test]
TO DISK = N'nul';
GO
USE [test];
GO
-- Create our table
IF OBJECT_ID('dbo.tbl','U') IS NOT NULL
BEGIN
DROP TABLE dbo.tbl;
END;
CREATE TABLE dbo.tbl
(
c1 BIGINT IDENTITY (1,1) NOT NULL
, c2 INT NOT NULL
) ON [PRIMARY];
GO
-- Insert 2,000,000 rows
INSERT INTO dbo.tbl
SELECT TOP 2000
number
FROM
master..spt_values
ORDER BY
number
GO 1000
Zu diesem Zeitpunkt benötigen wir einen oder mehrere Indizes, auf die die Sperrmechanismen der Isolationsstufe SERIALIZABLE einwirken können.
-- Add a clustered index
CREATE UNIQUE CLUSTERED INDEX CIX_tbl_c1
ON dbo.tbl (c1);
GO
-- Add a non-clustered index
CREATE NONCLUSTERED INDEX IX_tbl_c2
ON dbo.tbl (c2);
GO
Überprüfen wir nun, ob unsere 2.000.000 Zeilen erstellt wurden
SELECT
COUNT(*)
FROM
tbl;
Wir haben also unsere Datenbank, Tabelle, Indizes und Zeilen. Lassen Sie uns also das Experiment zum Knabbern von Löschvorgängen einrichten. Zunächst müssen wir entscheiden, wie ein typischer Knabber-Löschmechanismus am besten erstellt werden soll.
DECLARE
@BatchSize INT = 100
, @LowestValue BIGINT = 20000
, @HighestValue BIGINT = 20010
, @DeletedRowsCount BIGINT = 0
, @RowCount BIGINT = 1;
SET NOCOUNT ON;
GO
WHILE @DeletedRowsCount < ( @HighestValue - @LowestValue )
BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION
DELETE
FROM
dbo.tbl
WHERE
c1 IN (
SELECT TOP (@BatchSize)
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN @LowestValue AND @HighestValue
ORDER BY
c1
);
SET @RowCount = ROWCOUNT_BIG();
COMMIT TRANSACTION;
SET @DeletedRowsCount += @RowCount;
WAITFOR DELAY '000:00:00.025';
CHECKPOINT;
END;
Wie Sie sehen, habe ich die explizite Transaktion in die while-Schleife eingefügt. Wenn Sie das Löschen von Protokollen begrenzen möchten, können Sie es auch außerhalb der Schleife platzieren. Da wir uns im vollständigen Wiederherstellungsmodell befinden, möchten Sie möglicherweise häufiger Transaktionsprotokollsicherungen erstellen, während Sie die Löschvorgänge für das Nibbeln ausführen, um sicherzustellen, dass Ihr Transaktionsprotokoll nicht übermäßig anwächst.
Also, ich habe ein paar Ziele mit diesem Setup. Erstens möchte ich meine Tastensperren; Deshalb versuche ich, die Chargen so klein wie möglich zu halten. Ich möchte auch die Nebenläufigkeit auf meinem "gigantischen" Tisch nicht negativ beeinflussen. Also möchte ich meine Schlösser nehmen und sie so schnell wie möglich verlassen. Daher empfehle ich, dass Sie die Losgrößen klein halten.
Nun möchte ich ein sehr kurzes Beispiel für diese Löschroutine in Aktion geben. Wir müssen ein neues Fenster in SSMS öffnen und eine Zeile aus unserer Tabelle löschen. Ich werde dies innerhalb einer impliziten Transaktion mit der Standardisolationsstufe READ COMMITTED tun.
DELETE FROM
dbo.tbl
WHERE
c1 = 20005;
Wurde diese Zeile tatsächlich gelöscht?
SELECT
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN 20000 AND 20010;
Ja, es wurde gelöscht.
Um unsere Sperren anzuzeigen, öffnen wir jetzt ein neues Fenster in SSMS und fügen ein oder zwei Codefragmente hinzu. Ich verwende Adam Mechanics sp_whoisactive, das hier zu finden ist: sp_whoisactive
SELECT
DB_NAME(resource_database_id) AS DatabaseName
, resource_type
, request_mode
FROM
sys.dm_tran_locks
WHERE
DB_NAME(resource_database_id) = 'test'
AND resource_type = 'KEY'
ORDER BY
request_mode;
-- Our insert
sp_lock 55;
-- Our deletions
sp_lock 52;
-- Our active sessions
sp_whoisactive;
Nun sind wir bereit zu beginnen. Beginnen wir in einem neuen SSMS-Fenster eine explizite Transaktion, die versucht, die eine gelöschte Zeile erneut einzufügen. Zur gleichen Zeit werden wir unsere Knabber-Löschoperation auslösen.
Der Einfügungscode:
BEGIN TRANSACTION
SET IDENTITY_INSERT dbo.tbl ON;
INSERT INTO dbo.tbl
( c1 , c2 )
VALUES
( 20005 , 1 );
SET IDENTITY_INSERT dbo.tbl OFF;
--COMMIT TRANSACTION;
Beginnen wir beide Operationen mit der Einfügung und führen dann die Löschvorgänge aus. Wir können die Schlüsselbereichsschlösser und exklusiven Schlösser sehen.
Die Einfügung erzeugte diese Sperren:
Das knabbernde Löschen / Auswählen hält diese Sperren:
Unser Insert blockiert das Löschen wie erwartet:
Lassen Sie uns nun die Insert-Transaktion festschreiben und sehen, was los ist.
Und wie erwartet sind alle Transaktionen abgeschlossen. Nun müssen wir prüfen, ob es sich bei der Einfügung um ein Phantom handelte oder ob die Löschoperation es ebenfalls entfernt hat.
SELECT
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN 20000 AND 20015;
Tatsächlich wurde die Einfügung gelöscht. Daher war keine Phantomeinfügung zulässig.
Abschließend möchte ich sagen, dass die wahre Absicht dieser Übung nicht darin besteht, jede einzelne Sperre auf Zeilen-, Seiten- oder Tabellenebene nachzuverfolgen und zu ermitteln, ob ein Element eines Stapels gesperrt ist und daher unsere Löschoperation erforderlich machen würde warten. Das mag die Absicht der Fragesteller gewesen sein; Diese Aufgabe ist jedoch herkulisch und im Grunde unpraktisch, wenn nicht unmöglich. Das eigentliche Ziel ist es, sicherzustellen, dass keine unerwünschten Phänomene auftreten, sobald wir den Bereich unserer Charge mit eigenen Sperren isoliert und die Charge anschließend gelöscht haben. Die Isolationsstufe SERIALIZABLE erreicht dieses Ziel. Der Schlüssel ist, Ihre Knabbereien klein zu halten, Ihr Transaktionsprotokoll unter Kontrolle zu halten und unerwünschte Phänomene zu beseitigen.
Wenn Sie Geschwindigkeit wünschen, sollten Sie keine riesigen Tabellen erstellen, die nicht partitioniert werden können, und daher Partitionswechsel nicht verwenden, um die schnellsten Ergebnisse zu erzielen. Der Schlüssel zur Geschwindigkeit liegt in Partitionierung und Parallelität. Der Schlüssel zum Leiden sind Knabbereien und Live-Locking.
Bitte sag mir was du denkst.
Ich habe einige weitere Beispiele für die Isolationsstufe SERIALIZABLE in Aktion erstellt. Sie sollten unter den folgenden Links verfügbar sein.
Vorgang löschen
Operation einfügen
Gleichstellungsoperationen - Tastensperre für die nächsten Schlüsselwerte
Gleichstellungsoperationen - Singleton-Abruf vorhandener Daten
Gleichstellungsoperationen - Singleton-Abruf nicht vorhandener Daten
Ungleichungsoperationen - Schlüsselbereich sperrt den Bereich und die nächsten Schlüsselwerte