Métodos para acelerar un DELETE FROM <table> enorme sin cláusulas


37

Usando SQL Server 2005.

Estoy realizando un gran DELETE FROM sin cláusulas where. Básicamente es equivalente a una declaración TRUNCATE TABLE, excepto que no se me permite usar TRUNCATE. El problema es que la tabla es enorme: 10 millones de filas y lleva más de una hora completarla. ¿Hay alguna manera de hacerlo más rápido sin:

  • Usando Truncar
  • ¿Deshabilitar o descartar índices?

El t-log ya está en un disco separado.

Cualquier sugerencia bienvenida!


2
Si va a hacer esto mucho, considere dividir la mesa
Gaius

1
¿No puede usar TRUNCATE porque hay restricciones FK que hacen referencia a la tabla?
Nick Chammas

Respuestas:


39

Lo que puedes hacer es eliminar lotes de esta manera:

SELECT 'Starting' --sets @@ROWCOUNT
WHILE @@ROWCOUNT <> 0
    DELETE TOP (xxx) MyTable

Donde xxx es, digamos, 50000

Una modificación de esto, si desea eliminar un porcentaje muy alto de filas ...

SELECT col1, col2, ... INTO #Holdingtable
           FROM MyTable WHERE ..some condition..

SELECT 'Starting' --sets @@ROWCOUNT
WHILE @@ROWCOUNT <> 0
    DELETE TOP (xxx) MyTable WHERE ...

INSERT MyTable (col1, col2, ...)
           SELECT col1, col2, ... FROM #Holdingtable

3
@tuseau: cada eliminación requiere cierto espacio de registro en caso de error, para revertir. Una eliminación de 50 000 filas ocupa menos recursos / espacio que la eliminación de 10 millones de filas. Por supuesto, las copias de seguridad de registros aún se ejecutan, etc., y ocupan espacio, pero es más fácil en el servidor obtener muchos lotes pequeños que eliminar los grandes.
gbn

1
Gracias, la eliminación por lotes ayuda un poco, supongo que es la mejor opción.
Tuseau

2
@Phil Helmer: si la eliminación por lotes está en una transacción, no hay ganancia al usarla. De lo contrario, cada escritura de registro es más pequeña, lo cual es, simplemente, una carga más fácil
gbn

1
Otro comentario: la eliminación por lotes ayuda enormemente, y elimina la eliminación de 20 millones de filas de 1 hora 42 minutos a 3 minutos, ¡PERO asegúrese de que la tabla tenga un índice agrupado! Si es un montón, la cláusula TOP crea una especie en el plan de ejecución que niega cualquier mejora. Parece obvio después.
tuseau

2
@Noumenon: Asegura que @@ ROWCOUNT sea 1
gbn

21

Puede usar la cláusula TOP para hacer esto fácilmente:

WHILE (1=1)
BEGIN
    DELETE TOP(1000) FROM table
    IF @@ROWCOUNT < 1 BREAK
END

Los corchetes
dan

@gbn Eso está en SO. aquí sigue siendo 101 010.
bernd_k

7

Estoy de acuerdo con las sugerencias para agrupar sus eliminaciones en fragmentos manejables si no puede usar TRUNCATE, y me gusta la sugerencia de soltar / crear por su originalidad, pero tengo curiosidad sobre el siguiente comentario en su pregunta:

Básicamente es equivalente a una declaración TRUNCATE TABLE, excepto que no puedo usar TRUNCATE

Supongo que la razón de esta restricción tiene que ver con la seguridad que se debe otorgar para truncar directamente una tabla y el hecho de que le permitiría truncar tablas que no sean la que le preocupa.

Suponiendo que ese sea el caso, me pregunto si tener un procedimiento almacenado creado que use TRUNCATE TABLE y use "EXECUTE AS" se consideraría una alternativa viable para otorgar los derechos de seguridad necesarios para truncar la tabla directamente.

Con suerte, esto le dará la velocidad que necesita y al mismo tiempo abordará las preocupaciones de seguridad que su empresa puede tener al agregar su cuenta a la función db_ddladmin.

Otra ventaja de usar un procedimiento almacenado de esta manera es que el procedimiento almacenado en sí mismo podría bloquearse para que solo las cuentas específicas puedan usarlo.

Si por alguna razón esta no es una solución aceptable y su necesidad de eliminar los datos de esta tabla es algo que debe hacerse una vez al día / hora / etc., solicitaría que se creara un trabajo de Agente SQL para truncar la tabla a una hora programada cada día.

¡Espero que esto ayude!


5

Excepto truncar ... solo eliminar en lotes puede ayudarlo.

Podría soltar la tabla y volver a crearla, con todas las restricciones e índices, por supuesto. En Management Studio tiene la opción de crear una secuencia de comandos para colocar y crear una tabla, por lo que debería ser una opción trivial. Pero esto solo si se le permite hacer acciones DDL, lo que veo que no es realmente una opción.


Debido a que la aplicación está diseñada para operaciones concurrentes, alterar la estructura (DDL) y usar truncar no son opciones ... Supongo que la eliminación por lotes es la mejor disponible. Gracias sin embargo.
Tuseau

1

Dado que esta pregunta es una referencia tan importante, estoy publicando este código que realmente me ayudó a comprender la eliminación con bucles y también la mensajería dentro de un bucle para rastrear el progreso.

La consulta se modifica a partir de esta pregunta duplicada. Crédito a @RLF para la base de consultas.

CREATE TABLE #DelTest (ID INT IDENTITY, name NVARCHAR(128)); -- Build the test table
INSERT INTO #DelTest (name) SELECT name FROM sys.objects;  -- fill from system DB
SELECT COUNT(*) TableNamesContainingSys FROM #deltest WHERE name LIKE '%sys%'; -- check rowcount
go
DECLARE @HowMany INT;
DECLARE @RowsTouched INT;
DECLARE @TotalRowCount INT;
DECLARE @msg VARCHAR(100);
DECLARE @starttime DATETIME 
DECLARE @currenttime DATETIME 

SET @RowsTouched = 1; -- Needs to be >0 for loop to start
SET @TotalRowCount=0  -- Total rows deleted so far is 0
SET @HowMany = 5;     -- Variable to choose how many rows to delete per loop
SET @starttime=GETDATE()

WHILE @RowsTouched > 0
BEGIN
   DELETE TOP (@HowMany)
   FROM #DelTest 
   WHERE name LIKE '%sys%';

   SET @RowsTouched = @@ROWCOUNT; -- Rows deleted this loop
   SET @TotalRowCount = @TotalRowCount+@RowsTouched; -- Increment Total rows deleted count
   SET @currenttime = GETDATE();
   SELECT @msg='Deleted ' + CONVERT(VARCHAR(9),@TotalRowCount) + ' Records. Runtime so far is '+CONVERT(VARCHAR(30),DATEDIFF(MILLISECOND,@starttime,@currenttime))+' milliseconds.'
   RAISERROR(@msg, 0, 1) WITH NOWAIT;  -- Print message after every loop. Can't use the PRINT function as SQL buffers output in loops.  

END; 
SELECT COUNT(*) TableNamesContainingSys FROM #DelTest WHERE name LIKE '%sys%'; -- Check row count after loop finish
DROP TABLE #DelTest;
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.