Si entiendo la solicitud correctamente, el objetivo es eliminar lotes de filas, mientras que, al mismo tiempo, se producen operaciones DML en filas en toda la tabla. El objetivo es eliminar un lote; sin embargo, si cualquier fila subyacente contenida dentro del rango definido por dicho lote está bloqueada, entonces debemos omitir ese lote y pasar al siguiente lote. Luego debemos volver a los lotes que no se eliminaron previamente y volver a intentar nuestra lógica de eliminación original. Debemos repetir este ciclo hasta que se eliminen todos los lotes de filas requeridos.
Como se ha mencionado, es razonable usar una sugerencia READPAST y el nivel de aislamiento READ COMMITTED (predeterminado), para omitir los rangos pasados que pueden contener filas bloqueadas. Iré un paso más allá y recomendaré usar el nivel de aislamiento SERIALIZABLE y eliminar eliminaciones.
SQL Server usa bloqueos de rango clave para proteger un rango de filas implícitamente incluido en un conjunto de registros que se lee mediante una instrucción Transact-SQL mientras se usa el nivel de aislamiento de transacción serializable ... encuentre más aquí:
https://technet.microsoft.com /en-US/library/ms191272(v=SQL.105).aspx
Con las eliminaciones de mordiscos, nuestro objetivo es aislar un rango de filas y asegurarnos de que no se produzcan cambios en esas filas mientras las eliminamos, es decir, no queremos lecturas o inserciones fantasmas. El nivel de aislamiento serializable está destinado a resolver este problema.
Antes de demostrar mi solución, me gustaría agregar que tampoco recomiendo cambiar el nivel de aislamiento predeterminado de su base de datos a SERIALIZABLE ni recomiendo que mi solución sea la mejor. Simplemente deseo presentarlo y ver a dónde podemos ir desde aquí.
Algunas notas de mantenimiento:
- La versión de SQL Server que estoy usando es Microsoft SQL Server 2012 - 11.0.5343.0 (X64)
- Mi base de datos de prueba está utilizando el modelo de recuperación COMPLETO
Para comenzar mi experimento, configuraré una base de datos de prueba, una tabla de muestra y llenaré la tabla con 2,000,000 filas.
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
En este punto, necesitaremos uno o más índices sobre los que puedan actuar los mecanismos de bloqueo del nivel de aislamiento SERIALIZABLE.
-- 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
Ahora, verifiquemos que se crearon nuestras 2,000,000 filas
SELECT
COUNT(*)
FROM
tbl;
Entonces, tenemos nuestra base de datos, tabla, índices y filas. Entonces, configuremos el experimento para eliminar eliminaciones. Primero, debemos decidir cuál es la mejor manera de crear un mecanismo típico de eliminación de mordiscos.
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;
Como puede ver, coloqué la transacción explícita dentro del ciclo while. Si desea limitar las descargas de registros, no dude en colocarlo fuera del ciclo. Además, dado que estamos en el modelo de recuperación COMPLETA, es posible que desee crear copias de seguridad del registro de transacciones con mayor frecuencia mientras ejecuta sus operaciones de eliminación de mordiscos, para garantizar que su registro de transacciones no pueda crecer escandalosamente.
Entonces, tengo un par de objetivos con esta configuración. Primero, quiero mis cerraduras de rango de llave; entonces, trato de mantener los lotes lo más pequeños posible. Tampoco quiero impactar negativamente la concurrencia en mi tabla "gigantesca"; Entonces, quiero tomar mis cerraduras y dejarlas tan rápido como pueda. Por lo tanto, le recomiendo que reduzca el tamaño de sus lotes.
Ahora, quiero proporcionar un ejemplo muy breve de esta rutina de eliminación en acción. Debemos abrir una nueva ventana dentro de SSMS y eliminar una fila de nuestra tabla. Haré esto dentro de una transacción implícita usando el nivel de aislamiento predeterminado LEER COMPROMETIDO.
DELETE FROM
dbo.tbl
WHERE
c1 = 20005;
¿Esta fila se eliminó realmente?
SELECT
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN 20000 AND 20010;
Sí, fue eliminado.
Ahora, para ver nuestros bloqueos, abramos una nueva ventana dentro de SSMS y agreguemos un fragmento de código o dos. Estoy usando el sp_whoisactive de Adam Mechanic, que se puede encontrar aquí: 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;
Ahora estamos listos para comenzar. En una nueva ventana de SSMS, comencemos una transacción explícita que intentará volver a insertar la fila que eliminamos. Al mismo tiempo, activaremos nuestra operación de eliminación de mordiscos.
El código de inserción:
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;
Comencemos ambas operaciones comenzando con la inserción y seguidas por nuestras eliminaciones. Podemos ver las cerraduras de la gama de llaves y las cerraduras exclusivas.
El inserto generó estos bloqueos:
La eliminación / selección mordisqueada mantiene estos bloqueos:
Nuestra inserción está bloqueando nuestra eliminación como se esperaba:
Ahora, confirmemos la transacción de inserción y veamos qué sucede.
Y como se esperaba, todas las transacciones se completan. Ahora, debemos verificar si la inserción fue fantasma o si la operación de eliminación también la eliminó.
SELECT
c1
FROM
dbo.tbl
WHERE
c1 BETWEEN 20000 AND 20015;
De hecho, el inserto fue eliminado; entonces, no se permitió ningún inserto fantasma.
Entonces, en conclusión, creo que la verdadera intención de este ejercicio no es tratar de rastrear cada fila, página o bloqueo a nivel de tabla e intentar determinar si un elemento de un lote está bloqueado y, por lo tanto, requeriría nuestra operación de eliminación para Espere. Esa pudo haber sido la intención de los interrogadores; sin embargo, esa tarea es hercúlea y básicamente poco práctica, si no imposible. El objetivo real es garantizar que no surjan fenómenos no deseados una vez que hayamos aislado el rango de nuestro lote con bloqueos propios y luego precedamos a eliminar el lote. El nivel de aislamiento SERIALIZABLE logra este objetivo. La clave es mantener sus bocaditos pequeños, su registro de transacciones bajo control y eliminar los fenómenos no deseados.
Si desea velocidad, no cree tablas gigantescas que no puedan dividirse y, por lo tanto, no pueda utilizar el cambio de partición para obtener los resultados más rápidos. La clave para acelerar es el particionamiento y el paralelismo; la clave del sufrimiento son los mordiscos y el bloqueo en vivo.
Por favor déjame saber lo que piensa.
Creé algunos ejemplos adicionales del nivel de aislamiento SERIALIZABLE en acción. Deben estar disponibles en los enlaces a continuación.
Eliminar operación
Insertar operación
Operaciones de igualdad: bloqueos de rango clave en los siguientes valores clave
Operaciones de igualdad: recuperación de datos existentes de Singleton
Operaciones de igualdad: recuperación de datos no existentes de Singleton
Operaciones de desigualdad: bloqueos de rango clave en rango y valores clave siguientes