Frage

Ich habe kürzlich einen Fehler in einer Website gefunden und behoben, an der ich gearbeitet habe, die zu Millionen doppelter Datenreihen in einer Tabelle führte, die auch ohne sie (immer noch im Millionen) ziemlich groß sein wird. 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 in einem Schuss zu löschen, den Tisch für eine lange Zeit sperrt, was ich nach Möglichkeit vermeiden möchte. Die einzige Möglichkeit, wie ich diese Zeilen loswerden kann, ohne die Site abzunehmen (indem ich die Tabelle sperre), sind:

  1. Schreiben Sie ein Skript, das Tausende kleinerer Löschabfragen in einer Schleife ausführt. Dies wird theoretisch das Problem mit gesperrtem Tisch umgehen, da andere Abfragen es in die Warteschlange schaffen und zwischen den Deletten laufen können. Aber es wird immer noch die Last in der Datenbank ein wenig ansprechen und es wird lange dauern, bis es ausgeführt wird.
  2. Benennen Sie die Tabelle um und erstellen Sie die vorhandene Tabelle (sie wird jetzt leer sein). Dann machen Sie meine Reinigung auf dem umbenannten Tisch. Benennen Sie die neue Tabelle um, nennen Sie den alten zurück und verschmelzen die neuen Zeilen in den umbenannten Tisch. Auf diese Weise dauert es wesentlich mehr Schritte, sollte die Arbeit jedoch mit minimaler Unterbrechung erledigen. Der einzig schwierige Teil hier ist, dass der fragliche Tisch eine Berichtstabelle ist. Sobald er aus dem Weg umbenannt wird und der leere an seine Stelle gesetzt wird, werden alle historischen Berichte verschwinden, bis ich sie wieder eingesetzt habe. Außerdem könnte der Zusammenführungsprozess ein bisschen schmerzhaft sein, da die Daten gespeichert werden. Insgesamt ist dies gerade 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 abzubauen, und hoffentlich mit minimaler, wenn auch Unterbrechung der Benutzer? Wenn ich mit Nummer 2 oder einem anderen, ähnlichen Ansatz gehe, kann ich das Zeug plane, um spät abends zu laufen und am nächsten Morgen die Zusammenführung zu machen und die Benutzer im Voraus wissen zu lassen, das ist also keine große Sache. Ich suche nur zu sehen, ob jemand Ideen für einen besseren oder einfacheren Weg hat, um die Aufräumarbeiten zu machen.

War es hilfreich?

Lösung

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

Waschen, abspülen, wiederholen, bis keine Zeilen betroffen sind. Vielleicht in einem Skript, das zwischen den Iterationen für ein oder drei Sekunden schläft.

Andere Tipps

Ich würde auch empfehlen, Ihrer Tabelle einige Einschränkungen hinzuzufügen, um sicherzustellen, dass Ihnen dies nicht wieder passiert. Eine Million Zeilen mit 1000 pro Schuss werden 1000 Wiederholungen eines Skripts benötigen. Wenn das Skript alle 3,6 Sekunden lang ausgeführt wird, werden Sie in einer Stunde erledigt. Keine Bange. Es ist unwahrscheinlich, dass Ihre Kunden es bemerken.

Im Folgenden löscht sich 1000.000 Rekorde nacheinander.

 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önnen sie zusammenarbeiten und table_name löschen, wo in (id1, id2, .. idn) Ich bin mir auch sicher, dass mit viel Schwierigkeit

Ich hatte einen Anwendungsfall mit 1M+ Zeilen in der 25m+ Zeilen -Tabelle in der MySQL. Versuchte verschiedene Ansätze wie Batch Deletes (oben beschrieben).
Ich habe herausgefunden, dass der schnellste Weg (Kopie der erforderlichen Datensätze zu einer neuen Tabelle):

  1. Erstellen Sie eine temporäre Tabelle, die nur IDs enthält.

Table id_temp_table erstellen (temp_id int);

  1. IDs einfügen, die entfernt werden sollten:

In ID_TEMP_TABLE (TEMP_ID) einfügen, wählen Sie .....

  1. Erstellen Sie eine neue Tabelle Tabelle_New

  2. Fügen Sie alle Datensätze von Tabelle zu Tabelle_New ohne unnötige Zeilen ein, die in id_temp_table sind

in table_new einfügen .... wobei table_id nicht in (Wählen Sie Unterscheidung (temp_id) aus id_temp_table);

  1. Tische umbenennen

Der gesamte Prozess dauerte ~ 1 Stunden. In meinem Anwendungsfall dauerte einfache Löschen von Stapel auf 100 Datensätzen 10 Minuten.

Ich würde benutzen Mk-Archiver Aus dem ausgezeichneten Maatkit Dienstprogrammpaket (eine Reihe von Perl -Skripten für MySQL Management) Maatkit stammt von Baron Schwartz, dem Autor des O'Reilly -Buches "High Performance MySQL".

Das Ziel ist ein niedriger Auftrag mit geringer Auswirkung, um alte Daten aus der Tabelle zu knabbern, ohne sich auf die OLTP-Abfragen zu beeinflussen. Sie können die Daten in eine andere Tabelle einfügen, die sich nicht auf demselben Server befinden. Sie können es auch in eine Datei in einem Format schreiben, das für Ladedaten infile geeignet ist. Oder Sie können es auch nicht tun. In diesem Fall handelt es sich nur um eine inkrementelle Löschung.

Es wurde bereits für die Archivierung Ihrer unerwünschten Zeilen in kleinen Chargen gebaut.

Keine Installation erforderlich, einfach greifen http://www.maatkit.org/get/mk-archiver und führen Sie Perldoc für die Dokumentation aus (oder lesen Sie die Website).

Ich hatte ein ähnliches Problem. Wir hatten eine wirklich große Tabelle, ungefähr 500 GB Größe ohne Partitionierung und nur einen Index in der Spalte Primary_key. Unser Meister war ein Hulk einer Maschine, 128 Kerne und 512 Gigs RAM, und wir hatten auch mehrere Sklaven. Wir haben ein paar Techniken ausprobiert, um die groß angelegte Löschung von Reihen zu bekämpfen. Ich werde sie alle hier vom schlimmsten bis am besten auflisten, was wir gefunden haben.

  1. Jeweils eine Reihe abrufen und löschen. Dies ist das absolut Schlimmste, das Sie tun könnten. Also haben wir das nicht einmal versucht.
  2. Fetching First 'X' Zeilen aus der Datenbank mit einer Limit -Abfrage in der Spalte Primary_key, dann die Zeilen -IDs überprüft, um in der Anwendung zu löschen und eine einzelne Löschabfrage mit einer Liste von primären_key -IDs zu entlassen. Also 2 Abfragen pro x 'Zeilen. Jetzt war dieser Ansatz in Ordnung, aber dies mit einem Batch -Job wurde in etwa 5 Millionen Zeilen in etwa 10 Minuten gelöscht, wodurch die Sklaven unserer MySQL -DB um 105 Sekunden zurückgeführt wurden. 105-Sekunden-Verzögerung bei 10-minütiger Aktivität. Also mussten wir aufhören.
  3. In dieser Technik haben wir eine Verzögerung von 50 ms zwischen unserem nachfolgenden Batch -Abruf und Löschungen der Größe 'x' jeweils eingeführt. Dies löste das Verzögerungsproblem, aber wir haben jetzt 1,2-1,3 Millionen Zeilen pro 10 Minuten gelöscht, verglichen mit 5 Millionen in Technik Nr. 2.
  4. Partitionation der Datenbanktabelle und dann das Löschen der gesamten Partitionen, wenn sie nicht benötigt werden. Dies ist die beste Lösung, die wir haben, aber es erfordert eine vorteilige Tabelle. Wir folgten Schritt 3, weil wir eine nicht partitionierte sehr alte Tabelle mit nur Indizierung in der Spalte Primary_key hatten. Die Schaffung einer Partition hätte zu viel Zeit in Anspruch genommen und wir waren in einem Krisenmodus. Hier sind einige Links mit der Partitionierung, die ich als hilfreich empfand- Offizielle MySQL -Referenz, Oracle DB Daily Partitioning.

Wenn Sie es sich also leisten können, den Luxus zu haben, eine Partition in Ihrem Tisch zu erstellen, entscheiden Sie sich für die Option Nr. 4. Andernfalls stecken Sie mit Option Nr. 3 fest.

Machen Sie es in Chargen von Let Let Say 2000 Zeilen gleichzeitig. Sich dazwischen verpflichten. Eine Million Zeilen sind nicht so viel und dies wird schnell sein, es sei denn, Sie haben viele Indizes auf der Tabelle.

Laut dem MySQL -Dokumentation, TRUNCATE TABLE ist eine schnelle Alternative zu DELETE FROM. Versuche dies:

TRUNCATE TABLE table_name

Ich habe es auf 50 m Reihen ausprobiert und es wurde innerhalb von zwei Minuten durchgeführt.

Hinweis: Schnellvorgänge sind nicht transaktionssicher; Ein Fehler tritt beim Versuch eines im Verlaufs einer aktiven Transaktion oder einer aktiven Tabellensperre auf

Für uns die DELETE WHERE %s ORDER BY %s LIMIT %d Die Antwort war keine Option, da die Kriterien langsam waren (eine nicht idexierte Spalte) und den Master treffen würde.

Wählen Sie aus einer Read-Replica eine Liste der Primärschlüssel aus, die Sie löschen möchten. Export mit dieser Art von Format:

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

Verwenden Sie das folgende Bash -Skript, um diese Eingabe zu erfassen und in Löschen von Anweisungen zu löschen Erfordert Bash ≥ 4 wegen von mapfile eingebaut]:

sql-chunker.sh (erinnere dich an chmod +x Ich und ändern Sie den Shebang, um auf Ihre ausführbare Bash 4 zu verweisen):

#!/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"

Wie so aufrufen:

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

Auf diese Weise erhalten Sie eine Datei mit so formatiertem Ausgangsausgang (ich habe eine Chargengröß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 Aussagen wie SO aus:

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

Für diejenigen, die nicht vertraut sind login-path, Es ist nur eine Abkürzung, sich anzumelden, ohne das Passwort in der Befehlszeile einzugeben.

Ich denke, die Langsamkeit ist auf MySQLs "Clustered Index" 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 eine Festplatte abgerufen werden muss, da der Datensatz auf der Festplatte genau dort den richtigen Primärschlüssel im Index gefunden hat.

In anderen Datenbanken ohne Clustered -Indizes enthält der Index selbst den Datensatz nicht, sondern nur einen "Offset" oder "Ort", der angibt, wo sich der Datensatz in der Tabellendatei befindet, und dann in dieser Datei ein zweiter Abruf durchgeführt werden muss, um die tatsächlichen Daten abzurufen .

Sie können sich vorstellen, wenn Sie einen Datensatz in einem Clustered -Index löschen, dass alle oben genannten Datensätze in der Tabelle nach unten verschoben werden müssen, um zu vermeiden kann dies geändert haben).

Zu wissen, was wir fanden und das wirklich in MySQL gelöscht, war, die Delets in umgekehrter Reihenfolge durchzuführen. Dies erzeugt die geringste Menge an Rekordbewegungen, da Sie vom Ende zuerst Datensätze löschen, was bedeutet, dass nachfolgende Deletten weniger Objekte zum Umzug haben.

Ich habe nichts dazu geschrieben, und es würde unbedingt ein Skript erfordern, aber eine andere Option besteht darin, eine neue, doppelte Tabelle zu erstellen und alle Zeilen auszuwählen, die Sie einhalten möchten. Verwenden Sie einen Auslöser, um ihn während des Abschlusses dieses Vorgangs auf dem Laufenden zu halten. Wenn es synchron ist (abzüglich der Reihen, die Sie fallen lassen möchten), benennen Sie beide Tabellen in einer Transaktion um, so dass der neue den Platz des alten einnimmt. Lass den alten Tisch fallen und voila!

Dies erfordert (offensichtlich) viel zusätzlicher Speicherplatz und kann Ihre E/A -Ressourcen besteuern, ansonsten kann aber viel schneller sein.

Abhängig von der Art der Daten oder im Notfall können Sie die alte Tabelle umbenennen und eine neue, leere Tabelle an seiner Stelle erstellen und die "Keeping" -Filzs in der neuen Tabelle in Ihrer Freizeit auswählen ...

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top