Domanda

Recentemente ho trovato e corretto un bug in un sito su cui stavo lavorando che ha provocato milioni di righe duplicate di dati in una tabella che sarà abbastanza grande anche senza di loro (ancora in milioni). Posso facilmente trovare questi file duplicati e in grado di eseguire una singola query di eliminazione di ucciderli tutti. Il problema è che il tentativo di eliminare questo molti file in un solo colpo si blocca il tavolo per un lungo periodo di tempo, che vorrei evitare, se possibile. Gli unici modi che posso vedere per sbarazzarsi di queste righe, senza necessità di smontare il sito (bloccando la tabella) sono:

  1. Scrivi uno script che verrà eseguito migliaia di query di eliminazione più piccoli in un ciclo. Questo in teoria ottenere intorno al problema tavolo bloccato per altre query saranno in grado di farlo nella coda e correre tra le eliminazioni. Ma sarà ancora picco il carico sul database di un po 'e ci vorrà molto tempo per l'esecuzione.
  2. Rinominare la tabella e ricreare la tabella esistente (che sarà ora essere vuota). Poi fare la mia pulizia sul tavolo rinominato. Rinominare la nuova tabella, il nome del vecchio indietro e unire le nuove righe nella tabella rinominato. Questo è il modo richiede molto più passi, ma dovrebbe ottenere il lavoro fatto con una minima interruzione. L'unica parte difficile è che la tabella in questione è una tabella dei rapporti, quindi una volta che è rinominato fuori strada e il vuoto mettere al suo posto tutti i rapporti storici vanno via fino a quando ho messo al suo posto. Inoltre il processo di fusione potrebbe essere un po 'di dolore a causa del tipo di dati che vengono memorizzati. Nel complesso questo è la mia probabile scelta al momento.

Mi stavo chiedendo se qualcun altro ha avuto questo problema prima e, in caso affermativo, come avete affrontato con esso senza necessità di smontare il sito e, si spera, con il minimo eventuali interruzioni per gli utenti? Se vado con il numero 2, o di un altro, simile, approccio, posso programmare la roba per funzionare a tarda notte e fare la fusione presto la mattina successiva e lasciare che gli utenti di conoscere prima del tempo, in modo che non è un affare enorme. Sto solo cercando di vedere se qualcuno ha qualche idea per una migliore, o più facile, modo di fare la pulizia.

È stato utile?

Soluzione

DELETE FROM `table`
WHERE (whatever criteria)
ORDER BY `id`
LIMIT 1000

Lavare, risciacquare, ripetere fino a zero righe interessate. Forse in uno script che dorme per un secondo o tre tra le iterazioni.

Altri suggerimenti

Consiglio anche l'aggiunta di alcuni vincoli al vostro tavolo per fare in modo che ciò non accada di nuovo a voi. Un milione di righe, a 1000 per il colpo, avranno 1000 ripetizioni di uno script per completare. Se lo script viene eseguito una volta ogni 3,6 secondi ti verrà fatto in un'ora. Nessun problema. I vostri clienti sono improbabili a notare.

la seguente elimina 1.000.000 record, uno alla volta.

 for i in `seq 1 1000`; do 
     mysql  -e "select id from table_name where (condition) order by id desc limit 1000 " | sed 's;/|;;g' | awk '{if(NR>1)print "delete from table_name where id = ",$1,";" }' | mysql; 
 done

è possibile raggruppare insieme e non eliminare table_name DOVE IN (ID1, ID2, .. IDN) im sicuro che anche w / o molta difficoltà

Ho avuto un caso d'uso di eliminare 1M + righe della tabella 25M + righe nella MySQL. Provato approcci diversi, come le eliminazioni batch (sopra descritto).
Ho scoperto che il modo più veloce (copia della documentazione richiesta per nuova tabella):

  1. Crea tabella temporanea che contiene solo gli ID.
  

CREATE TABLE id_temp_table (temp_id int);

  1. Inserisci ID che devono essere rimossi:
  

inserire in id_temp_table (temp_id)       selezionare .....

  1. Crea nuovo tavolo table_new

  2. Inserire tutti i record dalla tabella per table_new senza file inutili che sono id_temp_table

  

inserire in table_new .... dove non table_id IN (selezionare   distinti (temp_id) da id_temp_table);

  1. tavoli Rinomina

L'intero processo ha preso ~ 1 ora. Nel mio caso d'uso semplice cancellazione dei batch su 100 record sono voluti 10 minuti.

mk-archiviatore dalla eccellente Maatkit pacchetto utilities (una serie di script Perl per la gestione di MySQL) Maatkit è da Baron Schwartz, l'autore della O'Reilly " high performance MySQL" libro.

  

L'obiettivo è a basso impatto, forward-only   lavoro da sgranocchiare i vecchi dati dal   tavolo senza impattare le query OLTP   tanto. È possibile inserire i dati in un altro   tavolo, che non deve necessariamente essere lo stesso   server. Si può anche scrivere in un   file in un formato adatto per carico   DATA INFILE. Oppure si può fare né, in   qual caso è solo un incrementale   DELETE.

E 'già costruito per l'archiviazione le righe indesiderate in piccoli lotti e come bonus, è possibile salvare le righe eliminate in un file nel caso in cui si fallisce la query che seleziona le righe da rimuovere.

Non richiede installazione, basta afferrare http://www.maatkit.org/get/mk -archiver ed eseguire perldoc su di esso (o leggere il sito web) per la documentazione.

Ho affrontato un problema simile. Abbiamo avuto davvero un grande tavolo, circa 500 GB di dimensione senza partizionamento e uno solo un indice sulla colonna primary_key. Il nostro padrone era una carcassa di una macchina, 128 core e 512 GB di ram e abbiamo avuto più slave troppo. Abbiamo provato alcune tecniche per affrontare l'eliminazione su larga scala di righe. Io li elencarli tutti qui dal peggiore al migliore che abbiamo trovato -

  1. Recupero ed eliminazione di una riga alla volta. Questo è il peggiore in assoluto che si possa fare. Quindi, non abbiamo nemmeno provato questo.
  2. Recupero prime file 'X' dal database utilizzando una query limite alla colonna di primary_key, quindi controllare gli ID di riga per eliminare l'applicazione e sparare un solo query di eliminazione con un elenco di ID primary_key. Quindi, 2 query al file 'x'. Ora, questo approccio è stato bene, ma facendo questo utilizzando un processo batch cancellato circa 5 milioni di righe in 10 minuti o giù di lì, a causa della quale gli schiavi del nostro DB MySQL sono stati ritardati da 105 secondi. 105 secondi ritardo nell'attività 10 minuti. Così, ci siamo dovuti fermare.
  3. In questa tecnica, abbiamo introdotto un ritardo di 50 ms tra la nostra partita successiva recuperare e delezioni di dimensione 'X' ciascuno. Questo ha risolto il problema di ritardo, ma ci hanno ora l'eliminazione di 1,2-1,3 milioni di righe per 10 minuti rispetto a 5 milioni in tecnica # 2.
  4. Partizione la tabella del database e quindi eliminare le intere partizioni quando non è necessario. Questa è la soluzione migliore che abbiamo, ma richiede una tabella pre-partizionato. Abbiamo seguito passo 3 perché avevamo un tavolo non partizionata molto vecchio con solo indicizzazione sulla colonna primary_key. Creazione di una partizione avrebbe richiesto troppo tempo e siamo stati in una modalità di crisi. Ecco alcuni link relativi a partizionamento che ho trovato disponibile- MySQL Ufficiale Riferimento , DB Oracle quotidiana partizionamento .

Quindi, IMO, se si può permettere di avere il lusso di creare una partizione nella tabella, andare per l'opzione # 4, in caso contrario, si è bloccato con l'opzione # 3.

Do it in lotti di diciamo 2000 righe alla volta. Impegnarsi in-between. Un milione di righe non è più di tanto e questo sarà veloce, a meno che non si dispone di molti indici nella tabella.

Secondo il mysql documentazione , TRUNCATE TABLE è una veloce alternativa al DELETE FROM. Prova questo:

TRUNCATE TABLE table_name

Ho provato questo su 50M righe ed è stato fatto nel giro di due minuti.

Nota: troncare operazioni non sono transaction-safe; si verifica un errore quando si tenta uno nel corso di una transazione attiva o blocco di tabella attiva

Per noi, la risposta DELETE WHERE %s ORDER BY %s LIMIT %d non era un'opzione, in quanto i criteri di cui è stata lenta (una colonna non indicizzata), e avrebbe colpito master.

selezionare da una lettura replica un elenco di chiavi primarie che si desidera eliminare. Export con questo tipo di formato:

00669163-4514-4B50-B6E9-50BA232CA5EB
00679DE5-7659-4CD4-A919-6426A2831F35

Utilizza il seguente script bash per afferrare questo ingresso e pezzo in istruzioni DELETE [richiede bash ≥ 4 a causa di mapfile built-in ]:

sql-chunker.sh (ricordarsi di chmod +x me, e cambiare la baracca per puntare al tuo bash 4 eseguibile) :

#!/usr/local/Cellar/bash/4.4.12/bin/bash

# Expected input format:
: <<!
00669163-4514-4B50-B6E9-50BA232CA5EB
00669DE5-7659-4CD4-A919-6426A2831F35
!

if [ -z "$1" ]
  then
    echo "No chunk size supplied. Invoke: ./sql-chunker.sh 1000 ids.txt"
fi

if [ -z "$2" ]
  then
    echo "No file supplied. Invoke: ./sql-chunker.sh 1000 ids.txt"
fi

function join_by {
    local d=$1
    shift
    echo -n "$1"
    shift
    printf "%s" "${@/#/$d}"
}

while mapfile -t -n "$1" ary && ((${#ary[@]})); do
    printf "DELETE FROM my_cool_table WHERE id IN ('%s');\n" `join_by "','" "${ary[@]}"`
done < "$2"

Invoke in questo modo:

./sql-chunker.sh 1000 ids.txt > batch_1000.sql

Questo vi darà un file con output formattato in questo modo (ho usato una dimensione del lotto di 2):

DELETE FROM my_cool_table WHERE id IN ('006CC671-655A-432E-9164-D3C64191EDCE','006CD163-794A-4C3E-8206-D05D1A5EE01E');
DELETE FROM my_cool_table WHERE id IN ('006CD837-F1AD-4CCA-82A4-74356580CEBC','006CDA35-F132-4F2C-8054-0F1D6709388A');

Poi eseguire le istruzioni in questo modo:

mysql --login-path=master billing < batch_1000.sql

Per chi non conosce login-path, è solo una scorciatoia per accedere senza digitare la password nella riga di comando.

Credo che la lentezza è dovuta a "indice cluster" di MySQL in cui i record reali sono memorizzati all'interno l'indice della chiave primaria - secondo l'ordine dell'indice chiave primaria. Ciò significa accesso ad un record tramite la chiave primaria è estremamente veloce perché richiede solo disco recuperare perché il record sul disco proprio lì dove si pensa che la chiave primaria corretta nell'indice.

In altri database senza indici cluster l'indice stesso non detiene il record, ma solo un "offset" o "posizione" che indicano dove il record si trova nel file di tabella e poi una seconda fetch deve essere effettuato in quel file da recuperare i dati effettivi.

Si può immaginare quando si elimina un record in un indice cluster che tutti i record di cui sopra che record nella tabella devono essere spostati verso il basso per evitare buchi enormi creati nell'indice (beh questo è quello che mi ricordo di qualche anno fa, almeno -. versioni successive potrebbero essere cambiati questo)

Conoscere il sopra quello che abbiamo scoperto che in realtà accelerato cancella in MySQL è stato quello di eseguire le eliminazioni in ordine inverso. Questo produce la minor quantità di movimento record perché sei record eliminare dalla fine prima che significa che le eliminazioni successive hanno meno oggetti a trasferirsi.

Non ho sceneggiato nulla a che fare questo, e facendo in modo corretto sarebbe assolutamente bisogno di uno script, ma un'altra opzione è quella di creare un nuovo, duplicare tavolo e selezionare tutte le righe che si desidera conservare in esso. Utilizzare un trigger per tenerlo up-to-date, mentre il processo è completato. Quando è in sincronia (meno le righe che si desidera eliminare), rinominare entrambe le tabelle in una transazione, in modo che il nuovo prende il posto del vecchio. Eliminare la vecchia tabella, e voilà!

Questa (ovviamente) richiede un sacco di spazio su disco in più, e può tassare le vostre risorse di I / O, ma in caso contrario, può essere molto più veloce.

A seconda della natura dei dati o in caso di emergenza, è possibile rinominare la vecchia tabella e creare una nuova tabella vuota al suo posto, e selezionare "mantenere" le righe nella nuova tabella a vostro piacimento ...

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top