Tengo que eliminar más de 16 millones de registros de una tabla de filas de más de 221 millones y va extremadamente lento.
Le agradezco si comparte sugerencias para hacer el siguiente código más rápido:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
DECLARE @BATCHSIZE INT,
@ITERATION INT,
@TOTALROWS INT,
@MSG VARCHAR(500);
SET DEADLOCK_PRIORITY LOW;
SET @BATCHSIZE = 4500;
SET @ITERATION = 0;
SET @TOTALROWS = 0;
BEGIN TRY
BEGIN TRANSACTION;
WHILE @BATCHSIZE > 0
BEGIN
DELETE TOP (@BATCHSIZE) FROM MySourceTable
OUTPUT DELETED.*
INTO MyBackupTable
WHERE NOT EXISTS (
SELECT NULL AS Empty
FROM dbo.vendor AS v
WHERE VendorId = v.Id
);
SET @BATCHSIZE = @@ROWCOUNT;
SET @ITERATION = @ITERATION + 1;
SET @TOTALROWS = @TOTALROWS + @BATCHSIZE;
SET @MSG = CAST(GETDATE() AS VARCHAR) + ' Iteration: ' + CAST(@ITERATION AS VARCHAR) + ' Total deletes:' + CAST(@TOTALROWS AS VARCHAR) + ' Next Batch size:' + CAST(@BATCHSIZE AS VARCHAR);
PRINT @MSG;
COMMIT TRANSACTION;
CHECKPOINT;
END;
END TRY
BEGIN CATCH
IF @@ERROR <> 0
AND @@TRANCOUNT > 0
BEGIN
PRINT 'There is an error occured. The database update failed.';
ROLLBACK TRANSACTION;
END;
END CATCH;
GO
Plan de ejecución (limitado para 2 iteraciones)
VendorId
es PK y no está agrupado , donde este script no utiliza el índice agrupado . Hay otros 5 índices no únicos y no agrupados.
La tarea es "eliminar proveedores que no existen en otra tabla" y hacer una copia de seguridad en otra tabla. Tengo 3 tablas, vendors, SpecialVendors, SpecialVendorBackups
. Intento eliminar los SpecialVendors
que no existen en la Vendors
tabla y tener una copia de seguridad de los registros eliminados en caso de que lo que estoy haciendo esté mal y tenga que volver a colocarlos en una o dos semanas.
sql-server
query-performance
delete
cilerler
fuente
fuente
Respuestas:
El plan de ejecución muestra que está leyendo filas de un índice no agrupado en algún orden y luego realiza búsquedas para cada lectura de fila externa para evaluar el
NOT EXISTS
Estás eliminando el 7.2% de la tabla. 16,000,000 filas en 3,556 lotes de 4,500
Suponiendo que las filas que califican se distribuyen eventualmente en todo el índice, esto significa que eliminará aproximadamente 1 fila cada 13.8 filas.
Entonces, la iteración 1 leerá 62,156 filas y realizará tantas búsquedas de índice antes de encontrar 4,500 para eliminar.
la iteración 2 leerá 57.656 (62.156 - 4.500) filas que definitivamente no calificarán ignorando las actualizaciones concurrentes (ya que ya se han procesado) y luego otras 62.156 filas para obtener 4.500 para eliminar.
la iteración 3 leerá (2 * 57.656) + 62.156 filas y así sucesivamente hasta que finalmente la iteración 3.556 leerá (3.555 * 57.656) + 62.156 filas y realice tantas búsquedas.
Por lo tanto, el número de búsquedas de índice realizadas en todos los lotes es
SUM(1, 2, ..., 3554, 3555) * 57,656 + (3556 * 62156)
Cuál es
((3555 * 3556 / 2) * 57656) + (3556 * 62156)
- o364,652,494,976
Sugeriría que materialice las filas para eliminar primero en una tabla temporal
Y cambie la
DELETE
opción para eliminarWHERE PK IN (SELECT PK FROM #MyTempTable WHERE BatchNumber = @BatchNumber)
Es posible que aún deba incluir unNOT EXISTS
en laDELETE
consulta para atender las actualizaciones, ya que la tabla temporal se completó, pero esto debería ser mucho más eficiente, ya que solo tendrá que realizar 4.500 búsquedas por lote.fuente
PK
columna? (Creo que me está sugiriendo para mover los de tabla temporal por completo, pero quería comprobarlo)DELETE TOP (@BATCHSIZE) FROM MySourceTable
¿DELETE FROM MySourceTable
también debería indexar la tabla temporalCREATE TABLE #MyTempTable ( Id BIGINT, BatchNumber BIGINT, PRIMARY KEY(BatchNumber, Id) );
yVendorId
definitivamente es la PK por sí sola? ¿Tiene> 221 millones de vendedores diferentes?El plan de ejecución sugiere que cada ciclo sucesivo hará más trabajo que el ciclo anterior. Suponiendo que las filas para eliminar se distribuyen uniformemente en toda la tabla, el primer bucle deberá escanear alrededor de 4500 * 221000000/16000000 = 62156 filas para encontrar 4500 filas para eliminar. También hará el mismo número de búsquedas de índice agrupado contra la
vendor
tabla. Sin embargo, el segundo ciclo deberá leer más allá de las mismas 62156 - 4500 = 57656 filas que no eliminó la primera vez. Podríamos esperar que el segundo bucle escanee 120000 filasMySourceTable
y realice 120000 búsquedas contra lavendor
mesa. La cantidad de trabajo necesaria por ciclo aumenta a una velocidad lineal. Como una aproximación, podemos decir que el ciclo promedio necesitará leer 102516868 filas desdeMySourceTable
y para hacer 102516868 busca en contra de lavendor
mesa. Para eliminar 16 millones de filas con un tamaño de lote de 4500, su código debe hacer 16000000/4500 = 3556 bucles, por lo que la cantidad total de trabajo para completar su código es de alrededor de 364.5 mil millones de filas leídasMySourceTable
y 364.5 mil millones de búsquedas de índice.Un problema menor es que usa una variable local
@BATCHSIZE
en una expresión TOP sin unaRECOMPILE
u otra sugerencia. El optimizador de consultas no sabrá el valor de esa variable local al crear un plan. Asumirá que es igual a 100. En realidad, está eliminando 4500 filas en lugar de 100, y posiblemente podría terminar con un plan menos eficiente debido a esa discrepancia. La estimación de baja cardinalidad al insertar en una tabla también puede causar un impacto en el rendimiento. SQL Server podría elegir una API interna diferente para hacer inserciones si cree que necesita insertar 100 filas en lugar de 4500 filas.Una alternativa es simplemente insertar las claves principales / claves agrupadas de las filas que desea eliminar en una tabla temporal. Dependiendo del tamaño de sus columnas clave, esto podría encajar fácilmente en tempdb. Puede obtener un registro mínimo en ese caso, lo que significa que el registro de transacciones no explotará. También puede obtener un registro mínimo en cualquier base de datos con un modelo de recuperación de
SIMPLE
. Consulte el enlace para obtener más información sobre los requisitos.Si esa no es una opción, entonces debe cambiar su código para poder aprovechar el índice agrupado
MySourceTable
. La clave es escribir su código para que haga aproximadamente la misma cantidad de trabajo por ciclo. Puede hacerlo aprovechando el índice en lugar de simplemente escanear la tabla desde el principio cada vez. Escribí una publicación de blog que analiza algunos métodos diferentes de bucle. Los ejemplos en esa publicación se insertan en una tabla en lugar de eliminarse, pero debería poder adaptar el código.En el código de ejemplo a continuación, supongo que la clave principal y la clave agrupada de su
MySourceTable
. Escribí este código bastante rápido y no puedo probarlo:La parte clave está aquí:
Cada ciclo solo leerá 60000 filas
MySourceTable
. Eso debería dar como resultado un tamaño de eliminación promedio de 4500 filas por transacción y un tamaño de eliminación máximo de 60000 filas por transacción. Si quieres ser más conservador con un tamaño de lote más pequeño, también está bien. La@STARTID
variable avanza después de cada ciclo para que pueda evitar leer la misma fila más de una vez desde la tabla de origen.fuente
Dos pensamientos me vienen a la mente:
El retraso probablemente se deba a la indexación con ese volumen de datos. Intente soltar los índices, eliminar y reconstruir los índices.
O..
Puede ser más rápido copiar las filas que desea mantener en una tabla temporal, soltar la tabla con los 16 millones de filas y cambiar el nombre de la tabla temporal (o copiar a una nueva instancia de la tabla fuente).
fuente