Se capisco correttamente la richiesta, l'obiettivo è eliminare i batch di righe, mentre allo stesso tempo, le operazioni DML si verificano su righe in tutta la tabella. L'obiettivo è eliminare un batch; tuttavia, se tutte le righe sottostanti contenute nell'intervallo definito da detto batch sono bloccate, allora dobbiamo saltare quel batch e passare al batch successivo. Dobbiamo quindi tornare a tutti i batch che non sono stati precedentemente eliminati e riprovare la nostra logica di eliminazione originale. Dobbiamo ripetere questo ciclo fino all'eliminazione di tutti i lotti di righe richiesti.
Come è stato menzionato, è ragionevole utilizzare un suggerimento READPAST e il livello di isolamento READ COMMITTED (predefinito), al fine di saltare gli intervalli che possono contenere righe bloccate. Farò un ulteriore passo e consiglierò di utilizzare il livello di isolamento SERIALIZZABILE e le eliminazioni rosicchianti.
SQL Server utilizza i blocchi di intervallo di chiavi per proteggere un intervallo di righe implicitamente incluse in un set di record letto da un'istruzione Transact-SQL durante l'utilizzo del livello di isolamento delle transazioni serializzabile ... Maggiori informazioni qui:
https://technet.microsoft.com /en-US/library/ms191272(v=SQL.105).aspx
Con le eliminazioni di roditura, il nostro obiettivo è isolare un intervallo di righe e garantire che non si verifichino cambiamenti a tali righe mentre le stiamo eliminando, vale a dire che non vogliamo letture o inserimenti fantasma. Il livello di isolamento serializzabile ha lo scopo di risolvere questo problema.
Prima di dimostrare la mia soluzione, vorrei aggiungere che non sto nemmeno raccomandando di cambiare il livello di isolamento predefinito del database in SERIALIZZABILE, né sto raccomandando che la mia soluzione sia la migliore. Desidero semplicemente presentarlo e vedere dove possiamo andare da qui.
Alcune note di pulizia:
- La versione di SQL Server che sto utilizzando è Microsoft SQL Server 2012 - 11.0.5343.0 (X64)
- Il mio database di test utilizza il modello di recupero COMPLETO
Per iniziare il mio esperimento, creerò un database di test, una tabella di esempio e riempirò la tabella con 2.000.000 di righe.
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
A questo punto, avremo bisogno di uno o più indici su cui possano agire i meccanismi di blocco del livello di isolamento SERIALIZZABILE.
-- 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
Ora, controlliamo per vedere che sono state create le nostre 2.000.000 di righe
SELECT
COUNT(*)
FROM
tbl;
Quindi, abbiamo il nostro database, tabella, indici e righe. Quindi, impostiamo l'esperimento per le eliminazioni rosicchianti. Innanzitutto, dobbiamo decidere il modo migliore per creare un tipico meccanismo di eliminazione rodibile.
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;
Come puoi vedere, ho inserito la transazione esplicita all'interno del ciclo while. Se desideri limitare i flush dei log, sentiti libero di posizionarlo al di fuori del loop. Inoltre, poiché siamo nel modello di recupero COMPLETO, potresti voler creare backup del registro delle transazioni più spesso durante l'esecuzione delle tue operazioni di eliminazione di roditura, al fine di garantire che il registro delle transazioni possa essere impedito di crescere in modo oltraggioso.
Quindi, ho un paio di obiettivi con questa configurazione. In primo luogo, voglio i miei blocchi di intervallo di chiavi; quindi, provo a mantenere i lotti il più piccoli possibile. Inoltre, non voglio influire negativamente sulla concorrenza sul mio tavolo "gigantesco"; quindi, voglio prendere i miei lucchetti e lasciarli il più velocemente possibile. Quindi, ti consiglio di ridurre le dimensioni dei lotti.
Ora, voglio fornire un breve esempio di questa routine di cancellazione in azione. Dobbiamo aprire una nuova finestra in SSMS ed eliminare una riga dalla nostra tabella. Lo farò all'interno di una transazione implicita utilizzando il livello di isolamento READ COMMITTED predefinito.
DELETE FROM
dbo.tbl
WHERE
c1 = 20005;
Questa riga è stata effettivamente eliminata?
SELECT
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN 20000 AND 20010;
Sì, è stato cancellato.
Ora, per vedere i nostri blocchi, apriamo una nuova finestra in SSMS e aggiungiamo uno o due frammenti di codice. Sto usando sp_whoisactive di Adam Mechanic, che può essere trovato qui: 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;
Ora siamo pronti per iniziare. In una nuova finestra SSMS, iniziamo una transazione esplicita che tenterà di reinserire una riga che abbiamo eliminato. Allo stesso tempo, spegneremo la nostra operazione di eliminazione sgranocchiante.
Il codice di inserimento:
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;
Iniziamo entrambe le operazioni a partire dall'inserimento e seguite dalle nostre eliminazioni. Possiamo vedere i blocchi della gamma di chiavi e i blocchi esclusivi.
L'inserto ha generato questi blocchi:
Il roditore elimina / seleziona tiene questi blocchi:
Il nostro inserto sta bloccando la nostra eliminazione come previsto:
Ora, commettiamo la transazione di inserimento e vediamo cosa succede.
E come previsto, tutte le transazioni vengono completate. Ora, dobbiamo verificare se l'inserto era un fantasma o se anche l'operazione di eliminazione lo ha rimosso.
SELECT
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN 20000 AND 20015;
In effetti, l'inserto è stato eliminato; quindi, non è stato permesso l'inserimento di alcun fantasma.
Quindi, in conclusione, penso che la vera intenzione di questo esercizio non sia quella di provare a tracciare ogni singola riga, pagina o blocco a livello di tabella e provare a determinare se un elemento di un batch è bloccato e richiederebbe quindi la nostra operazione di cancellazione per aspettare. Questo potrebbe essere stato l'intento degli interroganti; tuttavia, tale compito è erculeo e sostanzialmente impraticabile se non impossibile. Il vero obiettivo è garantire che non si verifichino fenomeni indesiderati una volta che abbiamo isolato la gamma del nostro batch con blocchi di nostra proprietà e quindi precediamo l'eliminazione del batch. Il livello di isolamento SERIALIZZABILE raggiunge questo obiettivo. La chiave è mantenere piccoli gli stuzzichini, controllare il registro delle transazioni ed eliminare i fenomeni indesiderati.
Se vuoi la velocità, non creare tabelle gigantesche profonde che non possono essere partizionate e quindi non puoi usare il cambio di partizione per i risultati più veloci. La chiave per accelerare è il partizionamento e il parallelismo; la chiave della sofferenza sono stuzzichini e live-lock.
Per favore fatemi sapere cosa ne pensate.
Ho creato alcuni ulteriori esempi del livello di isolamento SERIALIZZABILE in azione. Dovrebbero essere disponibili ai link seguenti.
Elimina operazione
Inserisci operazione
Operazioni di uguaglianza: blocchi dell'intervallo di chiavi sui valori chiave successivi
Operazioni sull'uguaglianza - Singleton Fetch of Existent Data
Operazioni sull'uguaglianza - Singleton Fetch di dati inesistenti
Operazioni di disuguaglianza - Blocchi intervallo di chiavi su valori chiave intervallo e successivo