2009-05-22 15 views
11

Ho una tabella in SQL Server 2005 che contiene circa 4 miliardi di righe. Devo cancellare circa 2 miliardi di queste righe. Se provo a farlo in una singola transazione, il log delle transazioni si riempie e fallisce. Non ho nessuno spazio in più per rendere più grande il registro delle transazioni. Suppongo che il modo migliore per avanzare sia quello di raggruppare le istruzioni di cancellazione (in gruppi di ~ 10.000?).SQL Batched Elimina

Posso farlo probabilmente usando un cursore, ma è un modo standard/facile/intelligente per farlo?

P.S. Questa tabella non ha una colonna Identity come PK. Il PK è composto da una chiave straniera intera e da una data.

+0

Mmm, suona come dati periodici storici/... –

risposta

7

È possibile "rosicchiare" l'eliminazione, il che significa anche che non si causa un carico eccessivo sul database. Se i backup del t-log vengono eseguiti ogni 10 minuti, è consigliabile eseguire questa operazione una o due volte nello stesso intervallo. È possibile pianificare come un processo di SQL Agente

provare qualcosa di simile:

DECLARE @count int 
SET @count = 10000 

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

Questo sembra buono. E a partire dal 2005, puoi effettivamente fare: DELETE TOP (@count) FROM ... –

2

Bene, se si stesse utilizzando il partizionamento di SQL Server, ad esempio in base alla colonna della data, si sarebbero probabilmente eliminate le partizioni che non sono più necessarie. Forse una considerazione per una futura implementazione.

Penso che l'opzione migliore possa essere come dici tu, per eliminare i dati in batch più piccoli, piuttosto che in un colpo, in modo da evitare potenziali problemi di blocco.

Si potrebbe anche prendere in considerazione il seguente metodo:

  1. Copiare i dati da tenere in una tabella temporanea
  2. troncare la tabella originale per eliminare tutti i dati
  3. spostare tutto dalla tabella temporanea di nuovo nel tabella originale

Gli indici saranno anche ricostruiti quando i dati sono stati aggiunti alla tabella originale.

+0

Grazie per la risposta, abbiamo guardato a partizionamento, ma la sua non è pratico per noi di attuarlo al momentn (paritially a causa di questo problema: http: // support.microsoft.com/kb/924601). Per quanto riguarda la copia dei dati su una tabella temporanea: l'operazione richiede uno spazio di registrazione delle transazioni inferiore rispetto all'eliminazione di righe? –

+0

Forse sì perché non è necessario eseguire un'operazione DELETE. Una volta creata una copia della tabella, TRONCA la tabella di origine e quindi copia solo i dati che desideri conservare nella tabella di origine. Ti consiglio comunque di andare con l'eliminazione del batch, poiché vuoi davvero che tutte le operazioni vengano registrate per garantire la coerenza/recuperabilità del tuo database. –

3

Suoni come questo è un'operazione una tantum (spero per te) e non è necessario tornare a uno stato che è a metà di questa eliminazione batch - se questo è il motivo per cui non si passa alla transazione SEMPLICE modalità prima di eseguire e poi di nuovo a FULL quando hai finito?

In questo modo il registro delle transazioni non aumenterà di molto. Questo potrebbe non essere l'ideale nella maggior parte delle situazioni, ma non vedo nulla di sbagliato qui (assumendo come sopra non è necessario tornare a uno stato che si trova tra le eliminazioni).

si può fare questo nello script con smt come:

ALTER DATABASE myDB SET RECOVERY FULL/SIMPLE 

alternativa è possibile impostare un lavoro a ridursi registrare la transazione ogni dato intervallo di tempo - in tutta cancellazione è in esecuzione. Questo è un po 'brutto, ma penso che farebbe il trucco.

+0

Sì, è un'operazione unica :) Purtroppo, stiamo già utilizzando il ripristino semplice, ma anche con il ripristino semplice, il tlog (100 GB) si riempie quando si esegue l'eliminazione in una singola transazione. –

+1

Ciò che vale la pena menzionare qui è che invaliderà qualsiasi backup transazionale passando al ripristino semplice. Se questo non viene utilizzato, allora va bene (e in realtà uso molto in questo modo), ma in caso contrario è necessario un backup completo o differenziale per poter utilizzare nuovamente i backup transazionali. –

+0

E la soluzione "alternativa"/hack? :) – JohnIdol

8

Che cosa distingue le righe che si desidera eliminare da quelle che si desidera conservare? Sarà questo lavoro per voi:

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

La condizione where dovrebbe essere: WHERE DateTimeInserted

+0

Continuerà a registrare le eliminazioni, anche a lotti, riempiendo il registro delle transazioni. – cjk

+0

È possibile saltare la selezione se si ritiene che sia costosa (basta sostituirla con una condizione di uscita più semplice). Per quanto riguarda la crescita del log delle transazioni, penso che si possano fare alcuni trucchi con i checkpoint all'interno del ciclo con l'opzione "truncate on checkpoint" attivata. –

-1

La risposta breve è, non si può cancellare 2 miliardi di righe senza incorrere in una sorta di grande inattività del database.

L'opzione migliore potrebbe essere quella di copiare i dati in una tabella temporanea e troncare la tabella originale, ma questo riempirà il vostro tempDB e non userebbe meno la registrazione che l'eliminazione dei dati.

È necessario eliminare tutte le righe possibili finché il registro delle transazioni non si riempie, quindi troncarlo ogni volta.La risposta fornita da Stanislav Kniazev potrebbe essere modificata per fare ciò aumentando la dimensione del batch e aggiungendo una chiamata per troncare il file di registro.

2

Vorrei fare qualcosa di simile ai suggerimenti della tabella temporanea ma selezionerei in una nuova tabella permanente le righe da mantenere, rilasciare la tabella originale e rinominare quella nuova. Ciò dovrebbe avere un impatto del registro di trasmissione relativamente basso. Ovviamente ricorda di ricreare gli indici che sono richiesti sulla nuova tabella dopo averlo rinominato.

Solo due p'enneth.

2

Oltre a mettere questo in un batch con una dichiarazione per troncare il registro, si potrebbe anche voler provare questi trucchi:

  • Aggiungere criteri che corrisponde alla prima colonna nel vostro indice cluster in aggiunta ai tuoi altri criteri
  • rilasciare qualsiasi indici dal tavolo e poi riporli dopo l'eliminazione è fatto, se questo è possibile, e non sarà in terfere con qualsiasi altra cosa succede nel DB, ma mantenere l'indice cluster

Per il primo punto di cui sopra, per esempio, se il PK è di tipo cluster poi trovare una gamma che corrisponde a circa il numero di righe che si desidera eliminare ogni lotto e l'uso che:

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

sono d'accordo con le persone che ti vogliono loop su un insieme ridotto di record, questo sarà più veloce che cercare di fare tutta l'operazione in un unico passaggio. Puoi provare con il numero di record che dovresti includere nel ciclo. Circa 2000 alla volta sembra essere il punto debole nella maggior parte delle tabelle. Faccio grandi deltes, anche se alcuni hanno bisogno di quantità minori come 500. Dipende dal numero di chiavi di forign, dimensione del record, trigger ecc, quindi ci vorranno davvero alcuni sperimentano per trovare ciò di cui hai bisogno. Dipende anche da quanto sia pesante l'uso del tavolo. Una tabella ad accesso pesante avrà bisogno di ogni iterazione del ciclo per eseguire una quantità di tempo più breve. Se è possibile eseguire durante le ore di pausa, o ancora meglio in modalità utente singolo, è possibile avere più record eliminati in un ciclo.

Se non pensi di farlo in una notte durante le ore di riposo, potrebbe essere meglio progettare il ciclo con un contatore e fare un numero fisso di iterazioni ogni notte fino a quando non viene eseguito.

Inoltre, se si utilizza una transazione implicita anziché una esplicita, è possibile interrompere la query del ciclo in qualsiasi momento e i record già eliminati rimarranno cancellati tranne quelli nel ciclo corrente del ciclo. Molto più veloce del tentativo di rollback di mezzo milione di dischi perché hai bloccato il sistema.

Generalmente è consigliabile eseguire il backup di un database immediatamente prima di intraprendere un'operazione di questo tipo.

0

Ecco il mio esempio:

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

Puoi aggiungere qualche descrizione oltre il codice stesso. – Akshay