Gleichzeitige MySQL-Updates hängen mit InnoDB (auf Amazon RDS)


7

Ich habe ein Problem, bei dem mehrere MySQL-Updates gleichzeitig ausgeführt werden und einige Minuten dauern. Ich verwende InnoDB, daher bin ich verwirrt, warum dies passieren könnte, da bei jedem Update nur eine Zeile aktualisiert wird. Ich verwende auch eine m2.4xlarge RDS-Instanz (die größte, die sie kommen).

Stuck Update

Folgendes mache ich: Ich habe eine Tabelle mit ungefähr 100 Millionen Zeilen, wobei "Ansichten" eine Spalte (die indiziert ist) ist, und ich möchte die Ansichten für ungefähr 1 Million Zeilen aktualisieren. Auf mehreren verschiedenen Servern habe ich eine Schleife wie diese, in der jeder Server seine eigenen zu aktualisierenden Zeilen hat (Pseudocode):

mysql("set autocommit=0");
mysql("start transaction");

foreach($rows as $row) {
    mysql("update table set views=views+1 where id=$row[id]");
}

mysql("commit");

Dadurch werden alle Zeilen durchlaufen, die aktualisiert werden müssen. Es funktioniert perfekt, wenn die Anzahl der Server gering ist, wie etwa 4, aber wenn es auf 10+ ansteigt, hängen die Updates auf einmal im Status "Aktualisieren". Nichts sagt, dass es auf ein Schloss wartet, es ist nur "Aktualisieren". Dies geschieht für ungefähr 5 Minuten, wo es schließlich die Aktualisierungen vornimmt und die Schleife durchläuft und schließlich wieder passiert.

Ich suche nicht nach alternativen Möglichkeiten, um die Updates durchzuführen. Dinge wie eine tmp-Tabelle haben und

update table,tmp_table set table.views = table.views+tmp_table.views where
  table.id = tmp_table.id

Sperren Sie alle Zeilen, die aktualisiert werden, bis sie alle fertig sind (dies können Stunden sein), was für mich nicht funktioniert. Sie müssen in diesen schrecklichen Schleifen sein.

Ich frage mich, warum sie im Status "Aktualisieren" stecken bleiben könnten und was ich tun kann, um dies zu verhindern.

tldr; Wenn mehr als 10 "Aktualisierungs" -Schleifen vorhanden sind, werden schließlich alle Aktualisierungen gleichzeitig aus einem unbekannten Grund gesperrt, bis sie sich entschließen, endgültig Aktualisierungen vorzunehmen und die Schleifen zu durchlaufen, damit sie Sekunden später erneut ausgeführt werden.

VARIABLEN ANZEIGEN: http://pastebin.com/NdmAeJrz

SHOW ENGINE INNODB STATUS: http://pastebin.com/Ubwu4F1h


1
Warum machen Sie das nicht in einer einzigen Abfrage?

@ IgnacioVazquez-Abrams Sie können dies nicht mit einer einzelnen Abfrage tun, ohne eine tmp-Tabelle wie die beschriebene zu verwenden.

2
Warum verwenden Sie Transaktionen für ein einfaches Update, bei dem nur eine Tabelle betroffen ist? Das fügt nur eine Menge unnötiger Verarbeitung hinzu, was zumindest einige Ihrer Leistungsprobleme erklärt.

@ JohnGardeniers Ich verstehe, dass Transaktionen aktualisierte Zeilen erst neu indizieren, wenn sie festgeschrieben werden.

Ist die Ansichtsspalte eine Zeichenfolge? Ich sehe viele davon: update images_new set views = views + '2',
Aaron Brown

Antworten:


11

Ich suche nicht nach alternativen Möglichkeiten, um die Updates durchzuführen. Wenn Sie Dinge wie eine tmp-Tabelle haben, werden alle Zeilen gesperrt, die aktualisiert werden, bis sie alle fertig sind (was Stunden sein kann), was für mich nicht funktioniert. Sie müssen in diesen schrecklichen Schleifen sein.

Ich stimme dir nicht zu.

Die Stärke eines RDBMS liegt in der Ausführung von Set-Operationen wie "Alle diese Zeilen aktualisieren plz". Vor diesem Hintergrund sollte Ihre Intuition Ihnen sagen, dass diese "schrecklichen Schleifen" nur unter sehr seltenen Umständen der beste Weg sind.

Werfen wir einen Blick auf Ihre aktuelle Update-Logik und verstehen, was sie tut.

Zunächst einmal ist die set autocommit=0Zeile in Ihrem Skript nicht erforderlich . Da Sie eine Transaktion unmittelbar danach explizit mit öffnen start transaction, wird sie autocommit automatisch deaktiviert, bis Sie die Transaktion mit COMMIToder beenden ROLLBACK.

Nun zum Kern der Logik: Sie haben alle diese einzelnen Aktualisierungen in einer großen Transaktion in die Schleife eingeschlossen. Wenn Ihre Absicht hinter den iterativen Aktualisierungen darin bestand, die Sperrung zu verringern und die Parallelität zu erhöhen, wird diese Absicht durch die umschlossene Transaktion zunichte gemacht. MySQL muss Sperren für jede Zeile beibehalten, die aktualisiert wird, bis die Transaktion festgeschrieben wird, damit alle auf einmal zurückgesetzt werden können, wenn die Transaktion fehlschlägt oder abgebrochen wird. Darüber hinaus statt im Voraus zu wissen , dass es im Begriff ist , diesen Bereich von Zeilen zu sperren (die MySQL Problem Sperren mit der entsprechenden Granularität ermöglichen würde) der Motor eine große Anzahl von Zeilensperren in Schnellfeuern auszustellen gezwungen wird. Angesichts der Tatsache, dass Sie 1 Million Zeilen aktualisieren, ist dies eine massive Belastung für die Engine.

Ich schlage zwei Lösungen vor:

  1. Aktivieren autocommitund entfernen Sie den Transaktions-Wrapper. MySQL kann dann jede Zeilensperre sofort nach Abschluss der Aktualisierung der Zeile aufheben. Es ist immer noch gezwungen, in kurzer Zeit eine große Anzahl von Sperren herauszugeben und freizugeben, daher bezweifle ich, dass dies eine angemessene Lösung für Sie ist. Wenn in der Mitte der Schleife ein Fehler auftritt, wird nichts zurückgesetzt, da die Arbeit nicht transaktionsgebunden ist.

  2. Stapeln Sie Ihre Updates in einer temporären Tabelle. Sie haben diese Lösung erwähnt und dann abgelehnt, aber ich wette, sie wird am besten funktionieren. Hast du es schon versucht? Ich würde zuerst das vollständige Millionen-Zeilen-Update testen. Wenn dies zu lange dauert, stapeln Sie die Arbeit in immer kleinere Teile, bis Sie den Sweet Spot gefunden haben: Die Stapel sind groß genug, um die gesamte Arbeit schnell zu erledigen, aber keine einzelne Charge blockiert andere Prozesse zu lange. Dies ist eine gängige Technik, die DBAs verwenden, wenn sie während Live-Vorgängen eine große Anzahl von Zeilen ändern müssen. Denken Sie daran, da Sie Ihr Ziel der gemeinsame Zugriff maximiert ist, halten autocommitauf und nicht wickeln jede dieser Arbeit in eine massive Transaktion so MySQL veröffentlicht seine Schleusen so schnell wie möglich.

    Beachten Sie, dass sich diese Lösung mit zunehmender Verkleinerung der Chargen möglicherweise der ersten annähert. Aus diesem Grund bin ich zuversichtlich, dass diese Lösung eine bessere Leistung erbringen wird: Wenn das Datenbankmodul seine Arbeit in Blöcke gruppieren kann, fliegt es.


Vielen Dank für die ausführliche Antwort! Ich habe den temporären Tisch ausprobiert und es dauert einfach viel zu lange. Die Idee, dies in Chargen zu tun, ist jedoch großartig. Ich werde das so schnell wie möglich ausprobieren. Vielen Dank!
Alan

@ Alan - Wie ist es gelaufen?
Nick Chammas

2
Ich stimme Ihrer Meinungsverschiedenheit zu. Ich liebe die Details. +1 !!!
RolandoMySQLDBA

1

Selbst bei InnoDB besteht immer die unmittelbare Gefahr eines Deadlocks. In diesem speziellen Fall kann ich sogar in InnoDB Zeilen sehen, die kopfüber in Deadlock-Situationen ausgeführt werden, da Sie Daten über den PRIMARY KEY der View-Tabellen aktualisieren. Dadurch wird eine aggressive Sperre innerhalb des Clustered-Index eingeleitet.

Sie können dies mit gesperrt sehen SHOW ENGINE INNODB STATUS\G

Ich beantwortete drei sehr schwierige Fragen zu einem ähnlichen Thema.

SELECT / UPDATE-Abfragen können beim Aktualisieren über den PRIMARY KEY Sperren für den gen_clust_index , auch bekannt als Clustered Index, durchführen.

Hier sind drei Fragen zum DBA- Stapelaustausch , die ich mit @RedBlueThing , der Person, die diese Fragen gestellt hat, aggressiv durchgesehen habe . @RedBlueThing hat Workarounds für seine Fragen gefunden.

In allen drei dieser Fragen umfasste eine Zeilensperre eine entsprechende Sperre im Clustered-Index derselben Tabelle. Benachbarte Schlüssel gesperrter Zeilen waren beteiligt und trugen somit zu den Problemen bei.

Moral der Geschichte: Deadlocks mit InnoDB sind immer noch eine Möglichkeit. Das Einrichten eines geeigneten Algorithmus für einzelne Sperren auf Zeilenebene und das individuelle Aktualisieren der betreffenden Zeilen ist viel sicherer als das tägliche Massenaktualisieren über mehrere Sperren auf Zeilenebene.

Stellen Sie sicher, dass Sie autocommit=1eine Tabelle auf diese Weise stark aktualisieren. Trotzdem führt das Aktualisieren einer Zeile in InnoDB dazu, dass alle Arten von MVCC- Daten um den vorherigen Inhalt der Zeile gelegt werden, um gleichzeitige Transaktionen zu ermöglichen. Aufgrund der Art des UPDATE werden viele MVCC-Daten generiert.


0

Wenn ich mir Ihren Innodb-Status ansehe, sehe ich, dass der neueste Deadlock mit der Ansichtstabelle auch auf diese Abfrage zurückzuführen ist:

update low_priority reddit_new 
join images_new on images_new.hash = reddit_new.hash 
set reddit_new.score = images_new.views 
where date > date(now() - interval 1 day)

Ist reddit_new.dateindiziert? Sind die Hash-Spalten aus beiden Tabellen indiziert?

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.