¿Cómo eliminar grandes datos de la tabla en SQL sin registro?


127

Tengo una gran tabla de datos. Hay 10 millones de registros en esta tabla.

¿Cuál es la mejor manera para esta consulta?

   Delete LargeTable where readTime < dateadd(MONTH,-7,GETDATE())

44
:) Me temo que a menos que esté dispuesto a escribir algún tipo de ETL para obtener todas las filas readTime> = dateadd (MONTH, -7, GETDATE ()) en otra tabla y luego emita una tabla Truncate y vuelva a colocar los datos usando ETL , no podrá evitar que se escriba en el registro
TMNT2014

El registro es una función de todo o nada de tener transacciones resistentes. Literalmente no tiene sentido no tener un registro para algunas operaciones pero no para otras, de lo contrario, el registro no sirve para nada.
Erik Philips

1
Exporta los datos que deseas conservar, trunca la tabla y luego vuelve a importarlos
Bohemian

Otra opción sería usar una variable de tabla que no esté registrada. Por lo tanto, almacene sus datos readTime> = dateadd (MES, -7, GETDATE ()) en una variable de tabla y luego trunca la tabla original y copia los datos de la variable de tabla. Sin embargo, mantendría una copia de seguridad de los datos en caso de que algo saliera mal y la tabla se truncara inadvertidamente. :) Y siempre haga una ejecución de prueba de su script en un entorno menor.
TMNT2014

Respuestas:


203
  1. Si está eliminando todas las filas de esa tabla, la opción más simple es Truncar la tabla, algo así como

    TRUNCATE TABLE LargeTable
    GO

    Truncar la tabla simplemente vaciará la tabla, no puede usar la cláusula WHERE para limitar las filas que se eliminarán y no se dispararán desencadenantes.

  2. Por otro lado, si está eliminando más del 80-90 por ciento de los datos, diga que si tiene un total de 11 millones de filas y desea eliminar 10 millones, otra forma sería insertar estos 1 millón de filas (registros que desea mantener ) a otra tabla de etapas. Trunca esta tabla grande e inserta de nuevo estas 1 millón de filas.

  3. O si los permisos / vistas u otros objetos que tienen esta tabla grande como su tabla subyacente no se ven afectados al soltar esta tabla, puede obtener esta cantidad relativamente pequeña de las filas en otra tabla, soltar esta tabla y crear otra tabla con el mismo esquema e importarlos filas de nuevo en esta tabla ex-Large.

  4. Una última opción que se me ocurre es cambiar la base de datos Recovery Mode to SIMPLEy luego eliminar filas en lotes más pequeños usando un ciclo while algo como esto ...

    DECLARE @Deleted_Rows INT;
    SET @Deleted_Rows = 1;
    
    
    WHILE (@Deleted_Rows > 0)
      BEGIN
       -- Delete some small number of rows at a time
         DELETE TOP (10000)  LargeTable 
         WHERE readTime < dateadd(MONTH,-7,GETDATE())
    
      SET @Deleted_Rows = @@ROWCOUNT;
    END

y no olvide volver a cambiar el modo de Recuperación a completo y creo que debe hacer una copia de seguridad para que sea completamente efectivo (los modos de cambio o recuperación).


14
También recuerde que si trunca una tabla, no puede tener ningún FK asociado con ella.
HLGEM

1
Pero, ¿cómo asegurarse de que está eliminando el 80-90% de los datos? Supongamos que solo tengo un rango de valores que deben eliminarse. Y tengo algunas mesas. Así que tengo que verificar cada uno de ellos y calcular el porcentaje, y si es alrededor del 30%, supongo que este método no es muy efectivo ... Estoy tratando de encontrar una solución óptima para un caso desconocido.
Archont

77
@Archont optimal solution for unknown caseese es el sueño, ¿no? Lamentablemente, no puede curar todas las enfermedades con una sola píldora; He sugerido algunas posibles soluciones para diferentes escenarios. Desafortunadamente, no hay una bala de astilla aquí.
M.Ali

55
Una cosa a tener en cuenta si elige la opción 4: Dependiendo de cómo se use la tabla, puede ser una mejor opción eliminar menos de 5000 filas a la vez para evitar la escalada de bloqueo .
Daniel

Si el recuento de registros para eliminar es mucho más grande que los registros que permanecerán en la tabla, encontré que la simple selección en la tabla temporal de los registros que permanecerán y se descartará la tabla original y el cambio de nombre de la tabla temporal es mucho más rápido. Dado que no utiliza la clave externa de ID de identidad en alguna parte.
Vladimir Bozic

95

La respuesta de @ m-ali es correcta, pero también tenga en cuenta que los registros podrían crecer mucho si no confirma la transacción después de cada fragmento y realiza un punto de control. Así es como lo haría y tomaría este artículo http://sqlperformance.com/2013/03/io-subsystem/chunk-deletes como referencia, con pruebas de rendimiento y gráficos:

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;


WHILE (@Deleted_Rows > 0)
  BEGIN

   BEGIN TRANSACTION

   -- Delete some small number of rows at a time
     DELETE TOP (10000)  LargeTable 
     WHERE readTime < dateadd(MONTH,-7,GETDATE())

     SET @Deleted_Rows = @@ROWCOUNT;

   COMMIT TRANSACTION
   CHECKPOINT -- for simple recovery model
END

1
Esta debería ser la respuesta aceptada en caso de que el espacio disponible en el disco sea limitado. Sin COMMIT TRANSACTIONy CHECKPOINTlos registros siguen creciendo. Gracias por dejar esto claro.
Gkoul

+1. Solo tenga en cuenta que es posible que desee comparar @Deleted_Rowscon 10000 o puede terminar con un bucle infinito debido a que elimina indefinidamente pequeños conjuntos de datos. Entonces WHILE (@Deleted_Rows = 10000), tan pronto como no haya una "página" completa de datos para eliminar, se detendrá. En su implementación, WHILE (@Deleted_Rows > 0)el ciclo while se ejecutará nuevamente incluso si solo eliminó una fila, y la próxima ejecución también podría encontrar una o dos filas para eliminar, lo que da como resultado un ciclo infinito.
NS du Toit

@NSduToit La cláusula WHERE está considerando registros que tienen al menos 7 meses de antigüedad, por lo que no habrá nuevos registros que cumplan con esa condición mientras realiza la eliminación.
Francisco Goldenstein

@FranciscoGoldenstein Bueno, la fecha que se utiliza en la consulta será diferente con cada iteración como se calcula repetidamente la fecha dentro del WHILEpropio bucle: dateadd(MONTH,-7,GETDATE()).
NS du Toit

@FranciscoGoldenstein Además, tal vez para otros casos de uso distintos a este, tal vez se agreguen nuevos datos a la tabla subyacente que darán como resultado nuevos registros que se pueden eliminar entre diferentes iteraciones del WHILEbucle.
NS du Toit

52

También puede usar GO + cuántas veces desea ejecutar la misma consulta.

DELETE TOP (10000)  [TARGETDATABASE].[SCHEMA].[TARGETTABLE] 
WHERE readTime < dateadd(MONTH,-1,GETDATE());
-- how many times you want the query to repeat
GO 100

Me gusta, está funcionando para mí. Accidentalmente inserté la misma fila en una tabla 26 millones de veces y necesité eliminar todas las apariciones, lo que en una sola declaración de eliminación se quedó sin memoria en el servidor, por lo que esta es una gran pregunta , ¿se detendrá a mitad de ciclo si se queda sin filas para eliminar?
ScottC

2
@ScottC, no es un bucle, solo repite la consulta (como un lote) y si se queda sin filas, no puede eliminar nada. Pero no se detendrá. obtendrá algo como (0 filas afectadas) si se queda sin filas que elimine.
Bunkerbuster

Ah, sí, descubrí que unos 5 minutos después de que publiqué mi pregunta, ya que mi eliminación terminó, ¡gracias, esto fue muy útil!
ScottC

1
¿Desde qué MS SQL Server se GO xxsupone que esta sintaxis funciona? Me sale un "no se encontró el procedimiento almacenado ''" error. Sin GOembargo, sin el comando funciona bien.
Abel

3
Hmm, parece que puedo ejecutarlo, y se ejecuta de hecho varias veces, pero en MS SQL Mgt Studio muestra la línea roja rizada con el error mencionado (pero F5-run funciona entonces)
Abel

11

@Francisco Goldenstein, solo una pequeña corrección. El COMMIT debe usarse después de establecer la variable; de ​​lo contrario, WHILE se ejecutará solo una vez:

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;

WHILE (@Deleted_Rows > 0)
BEGIN
    BEGIN TRANSACTION

    -- Delete some small number of rows at a time
    DELETE TOP (10000)  LargeTable 
    WHERE readTime < dateadd(MONTH,-7,GETDATE())

    SET @Deleted_Rows = @@ROWCOUNT;

    COMMIT TRANSACTION
    CHECKPOINT -- for simple recovery model

END

10

Esta variación de M. Ali está funcionando bien para mí. Elimina algunos, borra el registro y se repite. Estoy viendo el registro crecer, caer y comenzar de nuevo.

DECLARE @Deleted_Rows INT;
SET @Deleted_Rows = 1;
WHILE (@Deleted_Rows > 0)
  BEGIN
   -- Delete some small number of rows at a time
    delete top (100000) from InstallLog where DateTime between '2014-12-01' and '2015-02-01'
    SET @Deleted_Rows = @@ROWCOUNT;
    dbcc shrinkfile (MobiControlDB_log,0,truncateonly);
END

¡Esto fue muy útil! Lo modifiqué para parametrizar la # of rowseliminación a la vez, y también la WHEREcláusula. ¡Funciona de maravilla!
Shiva

7

Si está dispuesto (y puede) implementar la partición, esa es una técnica efectiva para eliminar grandes cantidades de datos con poca sobrecarga de tiempo de ejecución. Sin embargo, no es rentable para un ejercicio único.


4

Pude eliminar 19 millones de filas de mi tabla de 21 millones de filas en cuestión de minutos . Aquí está mi enfoque.

Si tiene una clave primaria de incremento automático en esta tabla, puede utilizar esta clave primaria.

  1. Obtenga el valor mínimo de la clave primaria de la tabla grande donde readTime <dateadd (MONTH, -7, GETDATE ()). (Agregue índice en readTime, si aún no está presente, este índice se eliminará de todos modos junto con la tabla en el paso 3.). Vamos a almacenarlo en una variable 'min_primary'

  2. Inserte todas las filas que tienen la clave primaria> min_primary en una tabla de preparación (tabla de memoria si el número de filas no es grande).

  3. Suelta la mesa grande.

  4. Recrea la mesa. Copie todas las filas de la tabla de etapas a la tabla principal.

  5. Suelta la mesa de ensayo.


3

Puede eliminar lotes pequeños usando un ciclo while, algo como esto:

DELETE TOP (10000)  LargeTable 
WHERE readTime < dateadd(MONTH,-7,GETDATE())
WHILE @@ROWCOUNT > 0
BEGIN
    DELETE TOP (10000)  LargeTable 
    WHERE readTime < dateadd(MONTH,-7,GETDATE())
END

2

Otro uso:

SET ROWCOUNT 1000 -- Buffer

DECLARE @DATE AS DATETIME = dateadd(MONTH,-7,GETDATE())

DELETE LargeTable  WHERE readTime < @DATE
WHILE @@ROWCOUNT > 0
BEGIN
   DELETE LargeTable  WHERE readTime < @DATE
END
SET ROWCOUNT 0

Opcional;

Si el registro de transacciones está habilitado, deshabilite los registros de transacciones.

ALTER DATABASE dbname SET RECOVERY SIMPLE;

2

Sintaxis más corta

select 1
WHILE (@@ROWCOUNT > 0)
BEGIN
  DELETE TOP (10000) LargeTable 
  WHERE readTime < dateadd(MONTH,-7,GETDATE())
END

1

Si está utilizando SQL Server 2016 o superior y si su tabla tiene particiones creadas en función de la columna que está tratando de eliminar (por ejemplo, la columna Timestamp), puede usar este nuevo comando para eliminar datos por particiones.

TABLA TRUNCADA CON (PARTICIONES ({|} [, ... n]))

Esto eliminará los datos solo en la (s) partición (es) seleccionada (s) y debería ser la forma más eficiente de eliminar datos de una parte de la tabla, ya que no creará registros de transacciones y se realizará tan rápido como el truncamiento normal pero sin que se eliminen todos los datos. de la mesa.

El inconveniente es que si su tabla no está configurada con partición, entonces necesita ir a la vieja escuela y eliminar los datos con un enfoque regular y luego volver a crear la tabla con particiones para que pueda hacer esto en el futuro, que es lo que hice. Agregué la creación y eliminación de particiones en el procedimiento de inserción. Tenía una tabla con 500 millones de filas, por lo que esta era la única opción para reducir el tiempo de eliminación.

Para obtener más detalles, consulte los siguientes enlaces: https://docs.microsoft.com/en-us/sql/t-sql/statements/truncate-table-transact-sql?view=sql-server-2017

Tabla truncada de SQL Server 2016 con particiones

A continuación se muestra lo que hice primero para eliminar los datos antes de poder volver a crear la tabla con particiones con los datos requeridos. Esta consulta se ejecutará durante días durante la ventana de tiempo especificada hasta que se eliminen los datos.

:connect <<ServerName>>
use <<DatabaseName>>

SET NOCOUNT ON;
DECLARE @Deleted_Rows INT;
DECLARE @loopnum INT;
DECLARE @msg varchar(100);
DECLARE @FlagDate datetime;
SET @FlagDate =  getdate() - 31;
SET @Deleted_Rows = 1;
SET @loopnum = 1;

/*while (getdate() < convert(datetime,'2018-11-08 14:00:00.000',120))
BEGIN
    RAISERROR( 'WAIT for START' ,0,1) WITH NOWAIT   
    WAITFOR DELAY '00:10:00'
END*/
RAISERROR( 'STARTING PURGE' ,0,1) WITH NOWAIT   

WHILE (1=1)
BEGIN
    WHILE (@Deleted_Rows > 0 AND (datepart(hh, getdate() ) >= 12 AND datepart(hh, getdate() ) <= 20)) -- (getdate() < convert(datetime,'2018-11-08 19:00:00.000',120) )
      BEGIN
       -- Delete some small number of rows at a time
         DELETE TOP (500000)  dbo.<<table_name>>
         WHERE timestamp_column < convert(datetime, @FlagDate,102)
         SET @Deleted_Rows = @@ROWCOUNT;
         WAITFOR DELAY '00:00:01'
         select @msg = 'ROWCOUNT' + convert(varchar,@Deleted_Rows);
         set @loopnum = @loopnum + 1
         if @loopnum > 1000
             begin 
                 begin try
                        DBCC SHRINKFILE (N'<<databasename>>_log' , 0, TRUNCATEONLY)
                        RAISERROR( @msg ,0,1) WITH NOWAIT
                 end try
                 begin catch
                     RAISERROR( 'DBCC SHRINK' ,0,1) WITH NOWAIT  
                 end catch
                 set @loopnum = 1
             end
        END
WAITFOR DELAY '00:10:00'
END 
select getdate()

0

Si digo sin bucle, puedo usar la GOTOinstrucción para eliminar una gran cantidad de registros usando el servidor SQL. exa.

 IsRepeat:
    DELETE TOP (10000)
    FROM <TableName>
    IF @@ROWCOUNT > 0
         GOTO IsRepeat

De esta manera, puede eliminar una gran cantidad de datos con un tamaño de eliminación más pequeño.

avíseme si requiere más información.

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.