2009-07-21 13 views
18

Ecco il problema che sto cercando di risolvere: ho recentemente completato un re-design strato di dati che mi permette di bilanciare il carico mio database su più frammenti. Al fine di mantenere cocci equilibrata, ho bisogno di essere in grado di migrare i dati da un frammento all'altro, che coinvolge la copia dal frammento A al frammento B, e quindi eliminando i record frammento A. Ma ho diversi tavoli che sono molto grandi, e hanno molte chiavi esterne puntate su di esse, quindi l'eliminazione di un singolo record dalla tabella può richiedere più di un secondo.Come velocizzare le eliminazioni da una tabella di database di grandi dimensioni?

In alcuni casi ho bisogno di eliminare milioni di record dalle tabelle, e ci vuole troppo tempo per essere pratico.

La disabilitazione chiavi esterne non è un'opzione. Anche l'eliminazione di batch di righe di grandi dimensioni non è un'opzione, poiché si tratta di un'applicazione di produzione e le eliminazioni più grandi bloccano troppe risorse, causando errori. Utilizzo Sql Server e conosco le tabelle partizionate, ma le restrizioni sul partizionamento (e le tariffe di licenza per l'edizione aziendale) sono così poco realistiche che non sono possibili.

Quando ho iniziato a lavorare su questo problema ho pensato che la parte difficile sarebbe stata scrivere l'algoritmo che capiva come eliminare le righe dal livello foglia fino alla cima del modello dati, in modo che nessun vincolo di chiave esterna venga violato la via. Ma risolvere il problema non mi ha aiutato, perché ci vogliono settimane per cancellare i record che devono scomparire dall'oggi al domani.

Ho già costruito un modo per contrassegnare i dati come virtualmente cancellati, quindi per quanto riguarda l'applicazione, i dati sono spariti, ma mi occupo ancora di file di dati di grandi dimensioni, backup di grandi dimensioni e query più lente a causa di la vastità dei tavoli.

Qualche idea? Ho già letto qui post più vecchi e non ho trovato nulla che possa aiutare.

+0

Perché non si può disattivare temporaneamente le chiavi esterne? Non si può eseguire questo durante un periodo di manutenzione e disabilitare l'accesso al sistema? – cjk

+0

Sarebbe un'opzione per rimuovere la necessità di fare questo processo di copia del tutto? Assicurando tramite alcuni algoritmi che i record siano divisi equamente tra i frammenti della parola go, piuttosto che dover spostare i record una volta che sono stati creati per bilanciarli. – AdaTheDev

+0

@AdaTheDev, per i nuovi frammenti non è un problema, ma la mia distribuzione iniziale era un backup-restore, e su ogni copia, segnare metà dei dati come virtualmente cancellati. Quindi quei due frammenti sono enormi. @ Mitch, il punto delle schegge è usare attrezzature più economiche, quindi non spenderò soldi per risolvere il problema. @ck, non sono sicuro che gli FK siano il vero problema. Penso che la maggiore percentuale di tempo trascorso sia l'eliminazione dell'indice cluster. –

risposta

1

È possibile creare nuovi file, copiare tutti, ma le righe "cancellato", poi scambiare i nomi sui tavoli. Infine, rilascia i vecchi tavoli. Se stai cancellando una grande percentuale dei record, allora potrebbe essere più veloce.

+0

Potrebbe essere qualcosa che potrei provare, ma stiamo parlando di tabelle con decine di milioni di record, diversi concerti per l'indice cluster. Dovrebbe essere possibile all'interno di una normale finestra di manutenzione. –

24

consultare: Optimizing Delete on SQL Server

Questo MS articolo di supporto potrebbe essere di interesse: How to resolve blocking problems that are caused by lock escalation in SQL Server:

spezzare le operazioni di batch di grandi dimensioni in più operazioni più piccole. Ad esempio, supponiamo che è stato eseguito il seguente query per rimuovere le diverse centinaia di migliaia di vecchi record da una tabella di controllo , e poi che ritieni ha causato un blocco escalation che ha bloccato altri utenti:

DELETE FROM LogMessages WHERE LogDate < '2/1/2002'  

By la rimozione di questi record poche centinaio alla volta, è possibile ridurre drasticamente il numero di serrature che si accumulano per transazione e prevenire l'escalation di blocchi. Ad esempio :

SET ROWCOUNT 500 
delete_more: 
    DELETE FROM LogMessages WHERE LogDate < '2/1/2002' 
IF @@ROWCOUNT > 0 GOTO delete_more 
SET ROWCOUNT 0 

Ridurre serratura impronta della query rendendo la query più efficiente possibile. Scansioni grandi o grandi numeri di segnalazioni di segnalibri potrebbero aumentare la possibilità di blocco ; inoltre, aumenta la possibilità di deadlock e generalmente influenza negativamente la concorrenza e la prestazione .

+1

Questo è proprio quello che stavo per suggerire. – HLGEM

+1

@crokusek, per aggiungere al tuo commento, non influenzerà questo particolare batch poiché "SET ROWCOUNT 500" lo rende 500 per questo batch. Qualsiasi batch successivo potrebbe essere interessato se si aspetta che 'ROWCOUNT' (diverso da' @@ ROWCOUNT') sia qualcos'altro. –

+0

Questo è semplicemente fantastico. Non ho mai pensato di guardare il problema in questo modo. Tutto quello che posso dire è wow! – pimbrouwers

0

Se si utilizza SQL 2005 o 2008, è possibile che si utilizzi l'opzione "isolamento dello snapshot". Consente ai dati di rimanere visibili agli utenti mentre è in corso l'elaborazione dell'aggiornamento dei dati sottostanti e quindi rivela i dati non appena viene eseguito il commit. Anche se si eliminano 30 minuti per l'esecuzione, le applicazioni rimarranno online durante questo periodo.

Ecco un rapido innesco di bloccaggio snapshot:

http://www.mssqltips.com/tip.asp?tip=1081

Anche se si dovrebbe comunque cercare di accelerare la cancellazione in modo che sia il più velocemente possibile, questo può alleviare alcuni degli oneri.

+0

Il problema è che non sto parlando di 30 minuti. Sto parlando di decine di milioni di righe che mi richiedono più di 1 secondo per riga da eliminare. Ciò si aggiunge a mesi. –

+0

1 secondo per riga da eliminare è eccessivo. Che hardware? – TomTom

+0

@TomTom: Anche se è molto lento, non è inconcepibile. Forse l'hardware è lento, o contesa alta, o forse le eliminazioni sono a cascata, o c'è un indice cluster sul tavolo che stai cancellando causando un sacco di movimento fisico dei dati, o forse alcuni trigger di eliminazione che sono in esecuzione per ogni riga. Mentre puoi sicuramente fare alcune cose per cercare di affrontarlo, ci possono essere delle ragioni per cui è così lento. – SqlRyan

11
delete_more: 
    DELETE TOP(500) FROM LogMessages WHERE LogDate < '2/1/2002' 
IF @@ROWCOUNT > 0 GOTO delete_more 

Si potrebbe ottenere lo stesso risultato utilizzando SET ROWCOUNT come suggerito da Mitch ma according to MSDN non sarà supportata per DELETE e alcune altre operazioni in future versioni di SQL Server:

Uso SET ROWCOUNT volontà non influisce sulle istruzioni DELETE, INSERT e UPDATE in una versione futura di SQL Server. Evitare di utilizzare SET ROWCOUNT con le istruzioni DELETE, INSERT e UPDATE nel nuovo lavoro di sviluppo, e pianificare la modifica delle applicazioni che attualmente lo utilizzano. Per un comportamento simile a , utilizzare la sintassi TOP. Per ulteriori informazioni, vedere TOP (Transact-SQL).

+1

E RowCount presenta implicazioni relative agli ambiti http://stackoverflow.com/questions/5383761/scope-of-set-rowcount-in-sql. Ad esempio, come sapresti che all'inizio è stato zero, ad esempio quando viene ripristinato? – crokusek

1

Un altro suggerimento è di rinominare la tabella e aggiungere una colonna di stato. Quando status = 1 (cancellato), non lo si vuole mostrare. Quindi si crea una vista con lo stesso nome della tabella originale che seleziona dalla tabella quando lo stato è null o = 0 (a seconda di come lo si implementa). La cancellazione appare immediata all'utente e un processo in background può essere eseguito ogni quindici minuti eliminando i record eseguiti senza che nessuno a parte il dbas ne sia a conoscenza.

-1

ecco la soluzione al tuo problema.

DECLARE @RC AS INT 
SET @RC = -1 

WHILE @RC <> 0 
BEGIN 
    DELETE TOP(1000000) FROM [Archive_CBO_ODS].[CBO].[AckItem] WHERE [AckItemId] >= 300 
    SET @RC = @@ROWCOUNT 
    --SET @RC = 0 
END 
0

È possibile eliminare piccoli lotti con un ciclo while, qualcosa di simile:

DELETE TOP (10000) FROM LogMessages WHERE LogDate < '2/1/2002' 
WHILE @@ROWCOUNT > 0 
BEGIN 
    DELETE TOP (10000) FROM LogMessages WHERE LogDate < '2/1/2002' 
END 
Problemi correlati