Metodi per accelerare un enorme DELETE FROM <table> senza clausole


37

Utilizzando SQL Server 2005.

Sto eseguendo un enorme DELETE FROM senza clausole where. È sostanzialmente equivalente a un'istruzione TRUNCATE TABLE - tranne per il fatto che non mi è permesso usare TRUNCATE. Il problema è che la tabella è enorme: 10 milioni di righe e il completamento richiede più di un'ora. Esiste un modo per renderlo più veloce senza:

  • Utilizzo di Troncare
  • Disabilitare o eliminare gli indici?

Il t-log è già su un disco separato.

Eventuali suggerimenti sono benvenuti!


2
Se lo farai molto, prendi in considerazione la partizione del tavolo
Gaius

1
Non puoi usare TRUNCATE perché ci sono vincoli FK che fanno riferimento alla tabella?
Nick Chammas,

Risposte:


39

Quello che puoi fare è eliminare in batch in questo modo:

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

Dove xxx è, diciamo, 50000

Una modifica di questo, se si desidera rimuovere una percentuale molto alta di righe ...

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: ogni eliminazione richiede dello spazio di log in caso di errore, per il rollback. Un'eliminazione di righe di 50k richiede meno risorse / spazio dell'eliminazione di righe di 10m. Ovviamente, i backup dei log continuano a funzionare, ecc. E occupano spazio, ma sul server è più facile un sacco di piccoli lotti piuttosto che creare grandi quantità.
gbn

1
Grazie, l'eliminazione batch aiuta un po ', immagino sia l'opzione migliore.
tuseau

2
@Phil Helmer: se l'eliminazione batch è in una transazione, non viene utilizzato alcun guadagno. Altrimenti ogni scrittura del registro è più piccola, il che è, semplicemente, un caricamento più semplice
gbn

1
Un altro commento: l'eliminazione batch aiuta enormemente e richiede l'eliminazione di 20 milioni di righe da 1 ora 42 minuti a 3 minuti - MA assicurati che la tabella abbia un indice cluster! Se si tratta di un heap, la clausola TOP crea un ordinamento nel piano di esecuzione che annulla qualsiasi miglioramento. Sembra ovvio dopo.
tuseau,

2
@Noumenon: assicura che @@ ROWCOUNT sia 1
gbn

21

Puoi usare la clausola TOP per farlo facilmente:

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

Le parentesi graffe formattano il tuo codice
gbn

@gbn Questo è su SO. qui è ancora 101 010.
bernd_k

7

Sono d'accordo con i suggerimenti per raggruppare le tue eliminazioni in blocchi gestibili se non sei in grado di utilizzare TRUNCATE e mi piace il suggerimento di rilascio / creazione per la sua originalità, ma sono curioso del seguente commento nella tua domanda:

È sostanzialmente equivalente a un'istruzione TRUNCATE TABLE - tranne per il fatto che non mi è permesso usare TRUNCATE

Immagino che il motivo di questa restrizione abbia a che fare con la sicurezza che deve essere concessa per troncare direttamente una tabella e il fatto che ti consentirebbe di troncare tabelle diverse da quella di cui ti occupi.

Supponendo che sia così, mi chiedo se la creazione di una procedura memorizzata che utilizza TRUNCATE TABLE e che utilizza "EXECUTE AS" sia considerata una valida alternativa ai diritti di sicurezza necessari per troncare la tabella direttamente.

Speriamo che questo ti dia la velocità di cui hai bisogno, affrontando anche i problemi di sicurezza che la tua azienda potrebbe avere con l'aggiunta del tuo account al ruolo db_ddladmin.

Un altro vantaggio dell'utilizzo di una procedura memorizzata in questo modo è che la procedura memorizzata stessa potrebbe essere bloccata in modo che solo gli account specifici possano utilizzarla.

Se per qualche motivo questa non è una soluzione accettabile e la necessità di rimuovere i dati in questa tabella è qualcosa che deve essere fatto una volta al giorno / ora / ecc., Chiederei che sia stato creato un processo di SQL Agent per troncare la tabella ogni giorno a un'ora prestabilita.

Spero che sia di aiuto!


5

Tranne il troncamento .. solo l'eliminazione in batch può aiutarti.

È possibile eliminare la tabella e ricrearla, con tutti i vincoli e gli indici, fuori rotta. In Management Studio hai la possibilità di scrivere una tabella da eliminare e creare, quindi dovrebbe essere un'opzione banale. Ma questo solo se ti è permesso fare azioni DDL, che vedo non è davvero un'opzione.


Poiché l'applicazione è progettata per operazioni simultanee, la modifica della struttura (DDL) e l'utilizzo del troncamento non sono opzioni ... Immagino che l'eliminazione batch sia la migliore disponibile. Grazie comunque.
tuseau

1

Poiché questa domanda è un riferimento così importante, sto pubblicando questo codice che mi ha davvero aiutato a capire l'eliminazione con i loop e anche i messaggi all'interno di un loop per tenere traccia dei progressi.

La query viene modificata da questa domanda duplicata. Ringraziamo @RLF per la base di query.

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;
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.