2009-05-22 17 views
11

Tengo una tabla en SQL Server 2005 que tiene aproximadamente 4 mil millones de filas en ella. Necesito eliminar aproximadamente 2 mil millones de estas filas. Si intento hacerlo en una sola transacción, el registro de transacciones se llena y falla. No tengo espacio adicional para agrandar el registro de transacciones. Supongo que la mejor manera de avanzar es preparar por lotes las instrucciones de eliminación (en lotes de ~ 10,000?).SQL Batched Delete

Probablemente pueda hacer esto usando un cursor, pero ¿es la manera estándar/fácil/inteligente de hacer esto?

P.S. Esta tabla no tiene una columna de identidad como PK. El PK está formado por una clave externa entera y una fecha.

+0

Mmm, suena como datos periódicos históricos/... –

Respuesta

7

Puede 'picar' las eliminaciones lo que también significa que no causa una carga masiva en la base de datos. Si sus copias de seguridad de t-log se ejecutan cada 10 minutos, entonces debería estar bien ejecutar esto una o dos veces en el mismo intervalo. Usted puede programar como un trabajo del Agente SQL

intentar algo como esto:

DECLARE @count int 
SET @count = 10000 

    DELETE FROM table1 
    WHERE table1id IN (
     SELECT TOP (@count) tableid 
     FROM table1 
     WHERE x='y' 
    ) 
+3

Esto se ve bien. Y a partir de 2005, puede hacerlo: DELETE TOP (@count) FROM ... –

2

Bueno, si estuvieras usando SQL Server Partitioning, por ejemplo, en función de la columna de fecha, posiblemente habrías cambiado las particiones que ya no se requieren. Una consideración para una implementación futura tal vez.

Creo que la mejor opción puede ser la que usted dice, para eliminar los datos en lotes más pequeños, en lugar de hacerlo en un solo golpe, a fin de evitar posibles problemas de bloqueo.

También podría considerar el siguiente método:

  1. Copiar los datos para mantener en una tabla temporal
  2. truncar la tabla original para purgar todos los datos
  3. Mover todo de la tabla temporal de nuevo en el tabla original

Sus índices también se reconstruirán a medida que los datos se vuelvan a agregar a la tabla original.

+0

Gracias por la respuesta, hemos buscado en el de la distribución, pero no es práctico para nosotros para ponerlo en práctica en el momentn (paritialmente debido a este problema: http: // support.microsoft.com/kb/924601). Con respecto a la copia de datos a una tabla temporal: ¿esta operación requeriría menos espacio de registro de transacciones que la eliminación de filas? –

+0

Posiblemente sí porque no necesitaría emitir una operación DELETE. Una vez que haya creado una copia de la tabla, TRONCATE la tabla fuente y luego copia solo los datos que desea mantener en la tabla fuente. Sin embargo, recomendaría que vaya con la eliminación de lotes, ya que realmente desea que todas las operaciones se registren para garantizar la consistencia/recuperación de su base de datos. –

3

Parece que se trata de una operación única (espero que lo haga) y no necesita volver a un estado que está a la mitad de esta eliminación por lotes - si es así, ¿por qué no cambia a transacción SIMPLE? modo antes de ejecutar y luego de nuevo a COMPLETO cuando haya terminado?

De esta manera, el registro de transacciones no crecerá tanto. Esto podría no ser ideal en la mayoría de las situaciones, pero no veo nada incorrecto aquí (suponiendo que arriba no necesite volver a un estado que se encuentra entre sus eliminaciones).

se puede hacer esto en su guión con SMT como:

ALTER DATABASE myDB SET RECOVERY FULL/SIMPLE 

Alternativamente se puede configurar un trabajo para reducir el tamaño del registro de transacciones cada intervalo de tiempo determinado -, mientras que su borrado se está ejecutando. Esto es un poco malo, pero creo que sería el truco.

+0

Sí, es una operación única :) Desafortunadamente, ya estamos utilizando la recuperación simple, pero incluso con una recuperación simple, el tlog (100GB) se llena al hacer la eliminación en una sola transacción. –

+1

Lo que vale la pena mencionar aquí es que invalidará cualquier copia de seguridad transaccional al cambiar a recuperación simple. Si eso no se está utilizando, está bien (y de hecho lo uso mucho) pero, de lo contrario, se necesita una copia de seguridad completa o diferencial para poder utilizar las copias de seguridad transaccionales nuevamente. –

+0

¿qué pasa con la solución 'alternativa'/hackear? :) – JohnIdol

8

¿Qué distingue las filas que desea eliminar de las que desea conservar? Será este trabajo por usted:

while exists (select 1 from your_table where <your_condition>) 
delete top(10000) from your_table 
where <your_condition> 
+0

La condición donde sería básicamente: WHERE DateTimeInserted

+0

Aún registrará las eliminaciones, incluso en lotes, llenando el registro de transacciones. – cjk

+0

Puede omitir la selección si cree que es costosa (simplemente reemplácela con alguna condición de salida más simple). En cuanto al crecimiento del registro de transacciones, creo que puedes hacer algunos trucos con puntos de control dentro del ciclo con la opción "truncar en el punto de control" activada. –

-1

La respuesta corta es, no se puede borrar de 2 mil millones de filas sin incurrir en algún tipo de base de datos de tiempo de inactividad importante.

Su mejor opción puede ser copiar los datos en una tabla temporal y truncar la tabla original, pero esto llenará su tempDB y no utilizará menos el registro que la eliminación de los datos.

Tendrá que eliminar tantas filas como sea posible hasta que el registro de transacciones se llene, luego trunque cada vez.La respuesta proporcionada por Stanislav Kniazev se puede modificar para hacer esto aumentando el tamaño del lote y agregando una llamada para truncar el archivo de registro.

2

Haría algo similar a las sugerencias de tabla temporal, pero seleccionaría en una nueva tabla permanente las filas que desea conservar, soltaré la tabla original y luego cambiaría el nombre a la nueva. Esto debería tener un impacto de registro relativamente bajo. Obviamente, recuerde volver a crear los índices que se requieren en la nueva tabla después de que la haya cambiado de nombre.

Just my two p'enneth.

2

Además de poner esto en un lote con una declaración a truncar el registro, es posible que también desee probar estos trucos:

  • Añadir criterios que coincide con la primera columna en el índice agrupado, además de sus otros criterios
  • dejar caer los índices de la tabla y luego vuelva a colocarlas después de la eliminación se lleva a cabo si eso es posible y no en interfieren con cualquier otra cosa sucede en la base de datos, pero mantener el índice agrupado

Para el primer punto anterior, por ejemplo, si su PK está agrupado luego encontrar un intervalo que coincide aproximadamente con el número de filas que desea eliminar cada lote y el uso que:

DECLARE @max_id INT, @start_id INT, @end_id INT, @interval INT 
SELECT @start_id = MIN(id), @max_id = MAX(id) FROM My_Table 
SET @interval = 100000 -- You need to determine the right number here 
SET @end_id = @start_id + @interval 

WHILE (@start_id <= @max_id) 
BEGIN 
    DELETE FROM My_Table WHERE id BETWEEN @start_id AND @end_id AND <your criteria> 

    SET @start_id = @end_id + 1 
    SET @end_id = @end_id + @interval 
END 
0

Estoy de acuerdo con las personas que te quieren bucle sobre un conjunto más pequeño de los registros, esto va a ser más rápido que tratando de hacer toda la operación en un solo paso. Puede experimentar con la cantidad de registros que debe incluir en el ciclo. Alrededor de 2000 a la vez parece ser el punto ideal en la mayoría de las tablas en las que realizo grandes deltes, aunque algunas necesitan cantidades menores, como 500. Depende del número de claves, el tamaño del registro, los factores desencadenantes, etc., así que realmente tomará algunos experimentando para encontrar lo que necesita También depende de qué tan pesado sea el uso de la mesa. Una tabla muy visitada necesitará que cada iteración del ciclo se ejecute durante un período de tiempo más corto. Si puede ejecutar fuera de horas, o mejor aún en modo de usuario único, puede eliminar más registros en un bucle.

Si no cree que haga esto en una noche durante las horas libres, podría ser mejor diseñar el ciclo con un contador y solo hacer un número determinado de iteraciones cada noche hasta que finalice.

Además, si utiliza una transacción implícita en lugar de una explícita, puede eliminar la consulta de bucle en cualquier momento y los registros ya eliminados permanecerán eliminados, excepto los de la ronda actual del bucle. Mucho más rápido que tratar de deshacer medio millón de registros porque has detenido el sistema.

Por lo general, es una buena idea hacer una copia de seguridad de una base de datos inmediatamente antes de realizar una operación de esta naturaleza.

0

Aquí está mi ejemplo:

-- configure script 
-- Script limits - transaction per commit (default 10,000) 
-- And time to allow script to run (in seconds, default 2 hours) 
-- 
DECLARE @MAX INT 
DECLARE @MAXT INT 
-- 
-- These 4 variables are substituted by shell script. 
-- 
SET @MAX = $MAX 
SET @MAXT = $MAXT 
SET @TABLE = $TABLE 
SET @WHERE = $WHERE 

-- step 1 - Main loop 
DECLARE @continue INT 
-- deleted in one transaction 
DECLARE @deleted INT 
-- deleted total in script 
DECLARE @total INT 
SET @total = 0 
DECLARE @max_id INT, @start_id INT, @end_id INT, @interval INT 
SET @interval = @MAX 
SELECT @start_id = MIN(id), @max_id = MAX(id) from @TABLE 
SET @end_id = @start_id + @interval 

-- timing 
DECLARE @start DATETIME 
DECLARE @now DATETIME 
DECLARE @timee INT 
SET @start = GETDATE() 
-- 
SET @continue = 1 
IF OBJECT_ID (N'EntryID', 'U') IS NULL 
BEGIN 
    CREATE TABLE EntryID (startid INT) 
    INSERT INTO EntryID(startid) VALUES(@start_id) 
END 
    ELSE 
BEGIN 
    SELECT @start_id = startid FROM EntryID 
END 


WHILE (@continue = 1 AND @start_id <= @max_id) 
BEGIN 

    PRINT 'Start issued: ' + CONVERT(varchar(19), GETDATE(), 120) 
    BEGIN TRANSACTION 
     DELETE 
     FROM @TABLE 
     WHERE id BETWEEN @start_id AND @end_id AND @WHERE 
     SET @deleted = @@ROWCOUNT 
    UPDATE EntryID SET EntryID.startid = @end_id + 1 
    COMMIT 
    PRINT 'Deleted issued: ' + STR(@deleted) + ' records. ' + CONVERT(varchar(19), GETDATE(), 120) 
    SET @total = @total + @deleted 
    SET @start_id = @end_id + 1 
    SET @end_id = @end_id + @interval 
    IF @end_id > @max_id 
     SET @end_id = @max_id 

    SET @now = GETDATE() 
    SET @timee = DATEDIFF (second, @start, @now) 
    if @timee > @MAXT 
    BEGIN 
    PRINT 'Time limit exceeded for the script, exiting' 
    SET @continue = 0 
    END 
-- ELSE 
-- BEGIN 
--  SELECT @total 'Removed now', @timee 'Total time, seconds' 
-- END 
END 

SELECT @total 'Removed records', @timee 'Total time sec' , @start_id 'Next id', @max_id 'Max id', @continue 'COMPLETED? ' 
SELECT * from EntryID next_start_id 

GO 
+1

Es posible que desee agregar alguna descripción más allá del código en sí. – Akshay