Come eliminare grandi dati della tabella in SQL senza registro?


127

Ho una grande tabella di dati. Ci sono 10 milioni di record in questa tabella.

Qual è il modo migliore per questa query

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

4
:) Temo che a meno che tu non sia disposto a scrivere una sorta di ETL per ottenere tutte le righe readTime> = dateadd (MONTH, -7, GETDATE ()) in un'altra tabella e quindi emettere una tabella Truncate e rimettere i dati utilizzando ETL , non saresti in grado di impedirgli di scrivere nel registro
TMNT2014

La registrazione è una funzione totale o nulla dell'avere transazioni resilienti. Non ha letteralmente senso non avere un registro per alcune operazioni ma non per altre, altrimenti il ​​registro è inutile.
Erik Philips,

1
Esporta i dati che desideri conservare, tronca la tabella, quindi reimportali in
Boemia

Un'altra opzione sarebbe utilizzare una tabella variabile che non sono registrati. Quindi archiviare i dati readTime> = dateadd (MONTH, -7, GETDATE ()) in una variabile di tabella, quindi troncare la tabella originale e copiare nuovamente i dati dalla variabile di tabella. Terrei comunque un backup dei dati nel caso qualcosa andasse storto e la tabella venisse inavvertitamente troncata. :) E farei sempre un test del tuo script in un ambiente minore.
TMNT2014,

Risposte:


203
  1. Se stai eliminando tutte le righe in quella tabella, l'opzione più semplice è Troncare la tabella, qualcosa del genere

    TRUNCATE TABLE LargeTable
    GO

    La tabella di troncamento svuoterà semplicemente la tabella, non è possibile utilizzare la clausola WHERE per limitare le righe da eliminare e nessun trigger verrà attivato.

  2. D'altra parte, se si sta eliminando più dell'80-90 percento dei dati, dire se si dispone di un totale di 11 milioni di righe e si desidera eliminare 10 milioni in un altro modo sarebbe quello di inserire questi 1 milione di righe (record che si desidera conservare ) a un'altra tabella di gestione temporanea. Tronca questa tabella grande e inserisci di nuovo queste 1 milione di righe.

  3. Oppure se permessi / viste o altri oggetti che hanno questa grande tabella come la loro tabella sottostante non vengono influenzati dalla caduta di questa tabella, puoi ottenere queste quantità relativamente piccole di righe in un'altra tabella, rilasciare questa tabella e creare un'altra tabella con lo stesso schema e importarle righe di nuovo in questa tabella ex-Large.

  4. Un'ultima opzione che mi viene in mente è quella di modificare il database Recovery Mode to SIMPLEe quindi eliminare le righe in lotti più piccoli utilizzando un ciclo while qualcosa del genere ..

    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

e non dimenticarti di ripristinare la modalità di ripristino al massimo e penso che devi eseguire un backup per renderlo pienamente efficace (la modalità di modifica o di ripristino).


14
Ricorda inoltre che se si tronca una tabella, non è possibile associare alcun FK ad essa.
HLGEM,

1
Ma come essere sicuri di eliminare l'80-90% dei dati? Supponiamo di avere solo un intervallo di valori che dovrebbero essere eliminati. E ho alcuni tavoli. Quindi devo controllarli tutti e calcolare la percentuale, e se attorno al 30% immagino che questo metodo non sia molto efficace ... Sto cercando di trovare una soluzione ottimale per un caso sconosciuto.
Archont,

7
@Archont optimal solution for unknown caseche è il sogno non è vero? Sfortunatamente non puoi curare ogni malattia con una pillola; Ho suggerito alcune possibili soluzioni per diversi scenari. Purtroppo non esiste un proiettile a nastro.
M. Ali

5
Una cosa da notare se si sceglie l'opzione 4: A seconda di come viene utilizzata la tabella, potrebbe essere un'opzione migliore per eliminare meno di 5000 righe alla volta per evitare l' escalation del blocco .
Daniel,

Se il conteggio dei record da eliminare è molto più grande dei record che rimarranno nella tabella, ho scoperto che selezionare semplicemente nella tabella temporanea dei record che rimarranno e rilasciare la tabella originale e rinominare la tabella temporanea è molto più veloce. Dato che non usi la chiave esterna dell'ID identità da qualche parte.
Vladimir Bozic,

95

La risposta di @ m-ali è corretta, ma tieni anche presente che i log potrebbero crescere molto se non esegui il commit della transazione dopo ogni blocco ed esegui un checkpoint. Ecco come lo farei e prendere questo articolo http://sqlperformance.com/2013/03/io-subsystem/chunk-deletes come riferimento, con test delle prestazioni e grafici:

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
Questa dovrebbe essere la risposta accettata nel caso in cui lo spazio disponibile su disco sia limitato. Senza COMMIT TRANSACTIONe CHECKPOINTi registri continuano a crescere. Grazie per averlo chiarito.
gkoul

+1. Basta notare che potresti voler confrontare @Deleted_Rowscon 10000 o potresti finire con un ciclo infinito a causa della sua eliminazione indefinita di piccoli set di dati. Quindi WHILE (@Deleted_Rows = 10000)- non appena non ci fosse una "pagina" completa di dati per eliminarli, si fermerà. Nell'implementazione WHILE (@Deleted_Rows > 0), il ciclo while si eseguirà di nuovo anche se ha eliminato solo una riga e l'esecuzione successiva potrebbe anche trovare una riga o due da eliminare, risultando in un ciclo infinito.
NS du Toit,

@NSduToit la clausola WHERE sta prendendo in considerazione i record che hanno almeno 7 mesi, quindi non ci saranno nuovi record che soddisfano tale condizione mentre si sta eseguendo la cancellazione.
Francisco Goldenstein,

@FranciscoGoldenstein Ebbene, la data utilizzato nella query sarà diverso con ogni iterazione, come ripetutamente calcolare la data all'interno del WHILEciclo stesso: dateadd(MONTH,-7,GETDATE()).
NS du Toit,

@FranciscoGoldenstein Inoltre, forse per altri casi d'uso diversi da questo - forse nuovi dati vengono aggiunti alla tabella sottostante che comporterà nuovi record che possono essere eliminati tra diverse iterazioni del WHILEciclo.
NS du Toit,

52

Puoi anche utilizzare GO + quante volte desideri eseguire la stessa query.

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

Mi piace, funziona per me Ho accidentalmente inserito la stessa riga in una tabella 26 milioni di volte e ho dovuto eliminare tutte le occorrenze di essa, che in una singola istruzione di eliminazione ha esaurito la memoria sul server, quindi questa è un'ottima domanda , interromperà il ciclo intermedio se si esauriscono le righe da eliminare?
ScottC,

2
@ScottC, non è un ciclo, si limita a ripetere la query (come un batch) e se si esauriscono le righe non è possibile eliminare nulla. Ma non si fermerà. otterrai qualcosa del tipo (0 righe interessate) se si esauriscono le righe eliminate.
Bunkerbuster,

ah, sì, ho scoperto che circa 5 minuti dopo aver pubblicato la mia domanda, poiché la mia eliminazione è terminata, grazie, è stato molto utile!
ScottC,

1
Da quale MS SQL Server GO xxdovrebbe funzionare questa sintassi ? Ho un "Impossibile trovare la stored procedure ''" errore. Senza il GOcomando funziona bene però.
Abel,

3
Hmm, sembra che posso eseguirlo, e funziona davvero più volte, ma in MS SQL Mgt Studio mostra la linea rossa riccia con l'errore menzionato (ma l'esecuzione F5 funziona quindi)
Abel

11

@Francisco Goldenstein, solo una piccola correzione. COMMIT deve essere utilizzato dopo aver impostato la variabile, altrimenti WHILE verrà eseguito una sola volta:

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

Questa variazione di M. Ali funziona bene per me. Elimina alcuni, cancella il registro e si ripete. Sto guardando il registro crescere, rilasciare e ricominciare.

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

Questo è stato molto utile! L'ho modificato per parametrizzare il # of rowsda eliminare alla volta e anche la WHEREclausola. Funziona come un fascino!
Shiva,

7

Se si è disposti (e in grado) di implementare il partizionamento, questa è una tecnica efficace per rimuovere grandi quantità di dati con un sovraccarico di runtime. Tuttavia, non è conveniente per un esercizio una tantum.


4

Sono stato in grado di eliminare 19 milioni di righe dalla mia tabella di 21 milioni di righe in pochi minuti . Ecco il mio approccio.

Se in questa tabella è presente una chiave primaria a incremento automatico , è possibile utilizzare questa chiave primaria.

  1. Ottieni il valore minimo della chiave primaria della tabella grande in cui readTime <dateadd (MONTH, -7, GETDATE ()). (Aggiungi indice su readTime, se non già presente, questo indice verrà comunque eliminato insieme alla tabella al passaggio 3.). Memorizziamolo in una variabile 'min_primary'

  2. Inserire tutte le righe con chiave primaria> min_primary in una tabella di gestione temporanea (tabella di memoria se il numero di righe non è grande).

  3. Rilascia il grande tavolo.

  4. Ricrea il tavolo. Copia tutte le righe dalla tabella di gestione temporanea alla tabella principale.

  5. Rilasciare la tabella di gestione temporanea.


3

Puoi eliminare piccoli lotti usando un ciclo while, qualcosa del genere:

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

Un altro 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

Opzionale;

Se il registro delle transazioni è abilitato, disabilitare i registri delle transazioni.

ALTER DATABASE dbname SET RECOVERY SIMPLE;

2

Sintassi più breve

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

1

Se si utilizza SQL Server 2016 o versione successiva e se nella tabella sono state create partizioni basate sulla colonna che si sta tentando di eliminare (ad esempio la colonna Timestamp), è possibile utilizzare questo nuovo comando per eliminare i dati dalle partizioni.

TABELLA TRUNCATE CON (PARTITIONS ({|} [, ... n]))

Ciò eliminerà i dati solo nelle partizioni selezionate e dovrebbe essere il modo più efficiente per eliminare i dati da una parte della tabella poiché non creerà i registri delle transazioni e verrà eseguito con la stessa velocità del normale troncamento, ma senza eliminare tutti i dati dal tavolo.

Lo svantaggio è se la tua tabella non è configurata con la partizione, quindi devi andare alla vecchia scuola ed eliminare i dati con un approccio regolare e quindi ricreare la tabella con le partizioni in modo da poterlo fare in futuro, che è quello che ho fatto. Ho aggiunto la creazione e la cancellazione della partizione nella stessa procedura di inserimento. Avevo una tabella con 500 milioni di righe, quindi questa era l'unica opzione per ridurre i tempi di cancellazione.

Per maggiori dettagli, consultare i collegamenti seguenti: https://docs.microsoft.com/en-us/sql/t-sql/statements/truncate-table-transact-sql?view=sql-server-2017

SQL Server 2016 Troncare la tabella con le partizioni

Di seguito è riportato ciò che ho fatto per eliminare i dati prima di poter ricreare la tabella con le partizioni con i dati richiesti al suo interno. Questa query verrà eseguita per giorni durante l'intervallo di tempo specificato fino a quando i dati non vengono eliminati.

: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

Se dico senza loop, posso usare la GOTOdichiarazione per cancellare grandi quantità di record usando il server SQL. exa.

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

in questo modo è possibile eliminare grandi quantità di dati con dimensioni di eliminazione inferiori.

fatemi sapere se richiede maggiori informazioni.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.