Ich habe kürzlich einen Fehler in einer Site gefunden und behoben, an dem ich gearbeitet habe, der zu Millionen doppelter Datenzeilen in einer Tabelle führte, die auch ohne sie ziemlich groß sein wird (immer noch in Millionenhöhe). Ich kann diese doppelten Zeilen leicht finden und eine einzelne Löschabfrage ausführen, um sie alle zu töten. Das Problem ist, dass der Versuch, so viele Zeilen auf einmal zu löschen, die Tabelle für eine lange Zeit sperrt, was ich nach Möglichkeit vermeiden möchte. Die einzigen Möglichkeiten, die ich sehen kann, um diese Zeilen zu entfernen, ohne die Site zu entfernen (indem die Tabelle gesperrt wird), sind:
- Schreiben Sie ein Skript, das Tausende kleinerer Löschabfragen in einer Schleife ausführt. Dies umgeht theoretisch das Problem mit gesperrten Tabellen, da andere Abfragen in die Warteschlange gelangen und zwischen den Löschvorgängen ausgeführt werden können. Die Datenbank wird jedoch immer noch stark belastet, und die Ausführung wird lange dauern.
- Benennen Sie die Tabelle um und erstellen Sie die vorhandene Tabelle neu (sie ist jetzt leer). Führen Sie dann meine Bereinigung für die umbenannte Tabelle durch. Benennen Sie die neue Tabelle um, benennen Sie die alte zurück und führen Sie die neuen Zeilen in der umbenannten Tabelle zusammen. Dies erfordert wesentlich mehr Schritte, sollte aber die Arbeit mit minimaler Unterbrechung erledigen. Der einzige schwierige Teil hier ist, dass es sich bei der fraglichen Tabelle um eine Berichtstabelle handelt. Sobald sie aus dem Weg umbenannt und die leere an ihre Stelle gesetzt wurde, verschwinden alle historischen Berichte, bis ich sie wieder einrichte. Außerdem kann der Zusammenführungsprozess aufgrund der Art der gespeicherten Daten etwas schmerzhaft sein. Insgesamt ist dies momentan meine wahrscheinliche Wahl.
Ich habe mich nur gefragt, ob jemand anderes dieses Problem schon einmal hatte und wenn ja, wie Sie damit umgegangen sind, ohne die Website herunterzufahren und hoffentlich mit minimalen oder gar keinen Unterbrechungen für die Benutzer? Wenn ich mich für Nummer 2 oder einen anderen, ähnlichen Ansatz entscheide, kann ich festlegen, dass das Material spät in der Nacht ausgeführt wird und die Zusammenführung am nächsten Morgen früh durchgeführt wird. Lassen Sie die Benutzer dies einfach im Voraus wissen, sodass dies keine große Sache ist. Ich bin nur auf der Suche nach Ideen für eine bessere oder einfachere Art der Bereinigung.
quelle
Antworten:
DELETE FROM `table` WHERE (whatever criteria) ORDER BY `id` LIMIT 1000
Waschen, spülen, wiederholen, bis keine Reihen mehr betroffen sind. Vielleicht in einem Skript, das zwischen den Iterationen eine oder drei Sekunden lang schläft.
quelle
DELETE ... JOIN
mitORDER BY
oder kombinieren kannLIMIT
.Ich hatte einen Anwendungsfall des Löschens von 1M + Zeilen in der 25M + Zeilen Tabelle in MySQL. Versuchte verschiedene Ansätze wie Batch-Löschungen (oben beschrieben).
Ich habe herausgefunden, dass der schnellste Weg (Kopie der erforderlichen Datensätze in eine neue Tabelle):
Neue Tabelle erstellen table_new
Fügen Sie alle Datensätze von table nach table_new ein, ohne unnötige Zeilen in id_temp_table
Der gesamte Vorgang dauerte ca. 1 Stunde. In meinem Anwendungsfall dauerte das einfache Löschen eines Stapels auf 100 Datensätzen 10 Minuten.
quelle
Ich würde auch empfehlen, Ihrer Tabelle einige Einschränkungen hinzuzufügen, um sicherzustellen, dass Ihnen dies nicht noch einmal passiert. Für eine Million Zeilen mit 1000 pro Aufnahme sind 1000 Wiederholungen eines Skripts erforderlich. Wenn das Skript alle 3,6 Sekunden einmal ausgeführt wird, sind Sie in einer Stunde fertig. Keine Sorgen. Es ist unwahrscheinlich, dass Ihre Kunden es bemerken.
quelle
Im Folgenden werden nacheinander 1.000.000 Datensätze gelöscht.
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
Sie könnten sie zusammen gruppieren und Tabellennamen löschen, wobei IN (id1, id2, .. idN) sicher auch ohne große Schwierigkeiten ist
quelle
DELETE FROM table WHERE id <= 100000
dann 200000 usw.). Jede Charge dauerte zwischen 30 Sekunden und 1 Minute. Als ich zuvor versucht habe, 1.300.000 auf einmal zu löschen, wurde die Abfrage mindestens 30 Minuten lang ausgeführt, bevor sie fehlschlug.ERROR 2013 (HY000): Lost connection to MySQL server during query.
Ich habe diese Abfragen im MySQL-Client auf derselben virtuellen Maschine wie der Server ausgeführt, aber möglicherweise ist die Verbindung abgelaufen.Ich würde mk-archiver aus dem hervorragenden Maatkit- Dienstprogrammpaket (eine Reihe von Perl-Skripten für die MySQL-Verwaltung) verwenden. Maatkit stammt von Baron Schwartz, dem Autor des O'Reilly-Buches "High Performance MySQL".
Es wurde bereits für die Archivierung Ihrer unerwünschten Zeilen in kleinen Stapeln entwickelt und kann als Bonus die gelöschten Zeilen in einer Datei speichern, falls Sie die Abfrage vermasseln, mit der die zu entfernenden Zeilen ausgewählt werden.
Keine Installation erforderlich, greifen Sie einfach auf http://www.maatkit.org/get/mk-archiver zu und führen Sie perldoc darauf aus (oder lesen Sie die Website), um eine Dokumentation zu erhalten.
quelle
Hier ist die empfohlene Vorgehensweise:
rows_affected = 0 do { rows_affected = do_query( "DELETE FROM messages WHERE created < DATE_SUB(NOW(),INTERVAL 3 MONTH) LIMIT 10000" ) } while rows_affected > 0
Referenz MySQL High Performance
quelle
Für uns war die
DELETE WHERE %s ORDER BY %s LIMIT %d
Antwort keine Option, da die WHERE-Kriterien langsam waren (eine nicht indizierte Spalte) und den Master treffen würden.Wählen Sie aus einer Lesereplik eine Liste der Primärschlüssel aus, die Sie löschen möchten. Exportieren Sie mit diesem Format:
Verwenden Sie das folgende Bash-Skript, um diese Eingabe zu erfassen und in DELETE-Anweisungen zu unterteilen [erfordert Bash ≥ 4 aufgrund der
mapfile
integrierten Funktion ]:sql-chunker.sh
(Denken Sie anchmod +x
mich und ändern Sie den Shebang so, dass er auf Ihre ausführbare Bash 4-Datei zeigt.) :#!/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"
Rufen Sie so auf:
Dadurch erhalten Sie eine Datei mit einer so formatierten Ausgabe (ich habe eine Stapelgröße von 2 verwendet):
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');
Führen Sie dann die Anweisungen folgendermaßen aus:
Für diejenigen, die nicht vertraut sind
login-path
, ist es nur eine Verknüpfung zum Anmelden, ohne ein Passwort in die Befehlszeile einzugeben.quelle
Ich hatte ein ähnliches Problem. Wir hatten eine wirklich große Tabelle mit einer Größe von ungefähr 500 GB ohne Partitionierung und einem einzigen Index für die Spalte primary_key. Unser Master war eine riesige Maschine, 128 Kerne und 512 GB RAM, und wir hatten auch mehrere Slaves. Wir haben einige Techniken ausprobiert, um das großflächige Löschen von Zeilen in Angriff zu nehmen. Ich werde sie alle hier auflisten, vom schlechtesten bis zum besten, das wir gefunden haben.
Also, IMO, wenn Sie es sich leisten können, den Luxus zu haben, eine Partition in Ihrer Tabelle zu erstellen, wählen Sie Option 4, andernfalls bleiben Sie bei Option 3.
quelle
Tun Sie dies in Stapeln von beispielsweise 2000 Zeilen gleichzeitig. Dazwischen festschreiben. Eine Million Zeilen sind nicht so viel und das wird schnell gehen, es sei denn, Sie haben viele Indizes in der Tabelle.
quelle
Ich denke, die Langsamkeit ist auf den "Clustered Index" von MySQl zurückzuführen, in dem die tatsächlichen Datensätze im Primärschlüsselindex gespeichert sind - in der Reihenfolge des Primärschlüsselindex. Dies bedeutet, dass der Zugriff auf einen Datensatz über den Primärschlüssel extrem schnell ist, da nur ein Festplattenabruf erforderlich ist, da der Datensatz auf der Festplatte genau dort liegt, wo er den richtigen Primärschlüssel im Index gefunden hat.
In anderen Datenbanken ohne Clustered-Indizes enthält der Index selbst nicht den Datensatz, sondern nur einen "Offset" oder "Speicherort", der angibt, wo sich der Datensatz in der Tabellendatei befindet. Anschließend muss in dieser Datei ein zweiter Abruf durchgeführt werden, um die tatsächlichen Daten abzurufen .
Sie können sich vorstellen, dass beim Löschen eines Datensatzes in einem Clustered-Index alle Datensätze über diesem Datensatz in der Tabelle nach unten verschoben werden müssen, um zu vermeiden, dass im Index massive Löcher entstehen (genau daran erinnere ich mich zumindest vor einigen Jahren - spätere Versionen kann dies geändert haben).
Wenn wir wissen, dass das Löschen in MySQL wirklich beschleunigt wurde, müssen wir die Löschvorgänge in umgekehrter Reihenfolge ausführen. Dies führt zu der geringsten Bewegung von Datensätzen, da Sie Datensätze vom Ende zuerst löschen, was bedeutet, dass nachfolgende Löschvorgänge weniger Objekte verschieben müssen.
quelle
Ich hatte eine wirklich geladene Basis, die ständig einige ältere Einträge löschen musste. Einige der Löschabfragen hingen an, sodass ich sie beenden musste. Wenn zu viele Löschvorgänge vorhanden waren, reagierte die gesamte Basis nicht mehr, sodass ich die parallelen Läufe einschränken musste. Also habe ich
cron job
jede Minute einen Lauf erstellt , der dieses Skript startet:#!/bin/bash ####################### # i_size=1000 max_delete_queries=10 sleep_interval=15 min_operations=8 max_query_time=1000 USER="user" PASS="super_secret_password" log_max_size=1000000 log_file="/var/tmp/clean_up.log" # ####################### touch $log_file log_file_size=`stat -c%s "$log_file"` if (( $log_file_size > $log_max_size )) then rm -f "$log_file" fi delete_queries=`mysql -u user -p$PASS -e "SELECT * FROM information_schema.processlist WHERE Command = 'Query' AND INFO LIKE 'DELETE FROM big.table WHERE result_timestamp %';"| grep Query|wc -l` ## -- here the hanging DELETE queries will be stopped mysql-u $USER -p$PASS -e "SELECT ID FROM information_schema.processlist WHERE Command = 'Query' AND INFO LIKE 'DELETE FROM big.table WHERE result_timestamp %'and TIME>$max_query_time;" |grep -v ID| while read -r id ; do echo "delete query stopped on `date`" >> $log_file mysql -u $USER -p$PASS -e "KILL $id;" done if (( $delete_queries > $max_delete_queries )) then sleep $sleep_interval delete_queries=`mysql-u $USER -p$PASS -e "SELECT * FROM information_schema.processlist WHERE Command = 'Query' AND INFO LIKE 'DELETE FROM big.table WHERE result_timestamp %';"| grep Query|wc -l` if (( $delete_queries > $max_delete_queries )) then sleep $sleep_interval delete_queries=`mysql -u $USER -p$PASS -e "SELECT * FROM information_schema.processlist WHERE Command = 'Query' AND INFO LIKE 'DELETE FROM big.table WHERE result_timestamp %';"| grep Query|wc -l` # -- if there are too many delete queries after the second wait # the table will be cleaned up by the next cron job if (( $delete_queries > $max_delete_queries )) then echo "clean-up skipped on `date`" >> $log_file exit 1 fi fi fi running_operations=`mysql-u $USER -p$PASS -p -e "SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST WHERE COMMAND != 'Sleep';"| wc -l` if (( $running_operations < $min_operations )) then # -- if the database is not too busy this bigger batch can be processed batch_size=$(($i_size * 5)) else batch_size=$i_size fi echo "starting clean-up on `date`" >> $log_file mysql-u $USER -p$PASS -e 'DELETE FROM big.table WHERE result_timestamp < UNIX_TIMESTAMP(DATE_SUB(NOW(), INTERVAL 31 DAY))*1000 limit '"$batch_size"';' if [ $? -eq 0 ]; then # -- if the sql command exited normally the exit code will be 0 echo "delete finished successfully on `date`" >> $log_file else echo "delete failed on `date`" >> $log_file fi
Damit habe ich ungefähr 2 Millionen Löschungen pro Tag erreicht, was für meinen Anwendungsfall in Ordnung war.
quelle
Ich habe nichts geschrieben, um dies zu tun, und um es richtig zu machen, wäre unbedingt ein Skript erforderlich, aber eine andere Option besteht darin, eine neue, doppelte Tabelle zu erstellen und alle Zeilen auszuwählen, die Sie darin behalten möchten. Verwenden Sie einen Auslöser, um ihn während dieses Vorgangs auf dem neuesten Stand zu halten. Wenn es synchron ist (abzüglich der Zeilen, die Sie löschen möchten), benennen Sie beide Tabellen in einer Transaktion um, sodass die neue die alte ersetzt. Lass den alten Tisch fallen und voila!
Dies erfordert (offensichtlich) viel zusätzlichen Speicherplatz und kann Ihre E / A-Ressourcen belasten, kann aber ansonsten viel schneller sein.
Abhängig von der Art der Daten oder im Notfall können Sie die alte Tabelle umbenennen und an ihrer Stelle eine neue, leere Tabelle erstellen und die Zeilen "behalten" in der neuen Tabelle nach Belieben auswählen ...
quelle
Nach Angaben der MySQL - Dokumentation ,
TRUNCATE TABLE
ist eine schnelle Alternative zuDELETE FROM
. Versuche dies:Ich habe dies in 50 Millionen Reihen versucht und es war innerhalb von zwei Minuten erledigt.
Hinweis: Kürzungsvorgänge sind nicht transaktionssicher. Beim Versuch einer aktiven Transaktion oder einer aktiven Tabellensperre tritt ein Fehler auf
quelle