In meinen Produktionsfehlerprotokollen sehe ich gelegentlich:
SQLSTATE [HY000]: Allgemeiner Fehler: 1205 Wartezeit für Sperren überschritten; Versuchen Sie, die Transaktion neu zu starten
Ich weiß, welche Abfrage gerade versucht, auf die Datenbank zuzugreifen, aber gibt es eine Möglichkeit herauszufinden, welche Abfrage genau zu diesem Zeitpunkt gesperrt war?
Antworten:
Was dies verrät, ist das Wort Transaktion . Aus der Aussage geht hervor, dass die Abfrage versucht hat, mindestens eine Zeile in einer oder mehreren InnoDB-Tabellen zu ändern.
Da Sie die Abfrage kennen, sind alle Tabellen, auf die zugegriffen wird, Kandidaten für den Schuldigen.
Von dort sollten Sie laufen können
SHOW ENGINE INNODB STATUS\G
Sie sollten in der Lage sein, die betroffenen Tabellen zu sehen.
Sie erhalten alle Arten von zusätzlichen Sperr- und Mutex-Informationen.
Hier ist ein Beispiel von einem meiner Kunden:
Sie sollten in Betracht ziehen, den Wert für das Wartezeitlimit für die Sperre für InnoDB zu erhöhen , indem Sie das Zeitlimit für innodb_lock_wait_timeout festlegen . Der Standardwert beträgt 50 Sekunden
/etc/my.cnf
Mit dieser Zeile können Sie dauerhaft einen höheren Wert einstellenund starte mysql neu. Wenn Sie MySQL derzeit nicht neu starten können, führen Sie Folgendes aus:
Sie können es auch einfach für die Dauer Ihrer Sitzung einstellen
gefolgt von Ihrer Anfrage
quelle
innodb_lock_wait_timeout
Variable nur beim Serverstart festgelegt werden. Das InnoDB-Plugin kann beim Start festgelegt oder zur Laufzeit geändert werden und hat sowohl globale als auch Sitzungswerte.SQL Error (1064): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '\G' at line 1
SET GLOBAL innodb_lock_wait_timeout = 120;
erneut ausführen . Wenn/etc/my.cnf
die Option hat,innodb_lock_wait_timeout
wird für Sie eingestellt. Nicht jeder hat das SUPER-Privileg, es global für alle anderen zu ändern ( dev.mysql.com/doc/refman/5.6/en/… )Wie jemand in einem der vielen SO-Threads zu diesem Problem erwähnt hat: Manchmal wird der Prozess, der die Tabelle gesperrt hat, in der Prozessliste als schlafend angezeigt! Ich riss mir die Haare aus, bis ich alle schlafenden Fäden getötet hatte, die in der fraglichen Datenbank geöffnet waren (zu diesem Zeitpunkt waren keine aktiv). Damit wurde die Tabelle endlich entsperrt und die Update-Abfrage ausgeführt.
Der Kommentator sagte etwas Ähnliches wie "Manchmal sperrt ein MySQL-Thread eine Tabelle und schläft dann, während er darauf wartet, dass etwas passiert, das nicht mit MySQL zu tun hat."
Nachdem
show engine innodb status
ich das Protokoll erneut überprüft hatte (nachdem ich den für die Sperre verantwortlichen Client aufgespürt hatte), stellte ich fest, dass der betreffende festsitzende Thread ganz unten in der Transaktionsliste unter den aktiven Abfragen aufgeführt war, die kurz vor dem Fehler standen wegen des eingefrorenen Schlosses raus:(Unsicher, ob die Nachricht "Trx-Leseansicht" mit der eingefrorenen Sperre zusammenhängt, aber im Gegensatz zu den anderen aktiven Transaktionen wird diese nicht mit der ausgegebenen Abfrage angezeigt und behauptet stattdessen, die Transaktion sei "bereinigen", hat jedoch mehrere Reihenschlösser)
Die Moral der Geschichte ist, dass eine Transaktion aktiv sein kann, obwohl der Thread schläft.
quelle
show processlist;
Anzeigen einer vollständigen Liste der derzeit ausgeführten Prozesse. Dies ist hilfreich , da es sich um eine komprimierte Version von handeltshow engine innodb status\g
. Wenn sich Ihre Datenbank auf einer Amazon RDS-Instanz befindet, können Sie auchCALL mysql.rds_kill(<thread_id>);
Threads beenden. Ich denke, es hat höhere Berechtigungen, weil es mir erlaubt hat, mehr Prozesse als einfach zukill <thread_id>;
Aufgrund der Beliebtheit von MySQL ist es kein Wunder, dass das Wartezeitlimit für Sperren überschritten wurde. Wenn Sie versuchen, die Transaktionsausnahme neu zu starten, wird SO so viel Aufmerksamkeit geschenkt.
Je mehr Konflikte Sie haben, desto größer ist die Wahrscheinlichkeit von Deadlocks, die eine DB-Engine durch Timeout einer der Deadlock-Transaktionen auflöst. Außerdem führen Transaktionen mit langer Laufzeit, bei denen eine große Anzahl von Einträgen geändert wurde (z. B.
UPDATE
oderDELETE
) Sperren (um Sperren zu vermeiden, um Anomalien beim Dirty-Write zu vermeiden, wie im High-Performance Java Persistence- Buch erläutert ), eher zu Konflikten mit anderen Transaktionen.Obwohl InnoDB MVCC, können Sie mithilfe der
FOR UPDATE
Klausel weiterhin explizite Sperren anfordern . Im Gegensatz zu anderen gängigen DBs (Oracle, MSSQL, PostgreSQL, DB2) wird MySQL jedochREPEATABLE_READ
als Standardisolationsstufe verwendet .Jetzt werden die Sperren, die Sie erworben haben (entweder durch Ändern von Zeilen oder durch explizites Sperren), für die Dauer der aktuell ausgeführten Transaktion gehalten. Wenn Sie eine gute Erklärung für den Unterschied zwischen
REPEATABLE_READ
undREAD COMMITTED
in Bezug auf das Sperren wünschen, lesen Sie bitte diesen Percona-Artikel .Deshalb: Je restriktiver die Isolationsstufe (
REPEATABLE_READ
,SERIALIZABLE
) ist, größer ist die Wahrscheinlichkeit eines Deadlocks. Dies ist kein Problem "per se", es ist ein Kompromiss.Sie können sehr gute Ergebnisse erzielen
READ_COMMITED
, da Sie bei Verwendung logischer Transaktionen, die sich über mehrere HTTP-Anforderungen erstrecken, eine Verhinderung verlorener Updates auf Anwendungsebene benötigen . Der optimistische Sperransatz zielt auf verlorene Aktualisierungen ab , die auch dann auftreten können, wenn Sie dieSERIALIZABLE
Isolationsstufe verwenden und gleichzeitig den Sperrkonflikt verringern, indem Sie die Verwendung zulassenREAD_COMMITED
.quelle
Für den Datensatz tritt die Ausnahme des Wartezeitlimits für Sperren auch dann auf, wenn ein Deadlock vorliegt und MySQL ihn nicht erkennen kann, sodass nur eine Zeitüberschreitung auftritt. Ein weiterer Grund könnte eine extrem lange laufende Abfrage sein, die jedoch leichter zu lösen / zu reparieren ist, und ich werde diesen Fall hier nicht beschreiben.
MySQL ist normalerweise in der Lage, Deadlocks zu verarbeiten, wenn sie innerhalb von zwei Transaktionen "richtig" erstellt wurden. MySQL beendet / rollt dann nur die eine Transaktion, die weniger Sperren besitzt (ist weniger wichtig, da dies weniger Zeilen betrifft) und lässt die andere beenden.
Nehmen wir nun an, es gibt zwei Prozesse A und B und 3 Transaktionen:
Dies ist eine sehr unglückliche Einrichtung, da MySQL keinen Deadlock erkennen kann (innerhalb von 3 Transaktionen). Also, was MySQL macht, ist ... nichts! Es wartet nur, da es nicht weiß, was zu tun ist. Es wird gewartet, bis die erste erfasste Sperre das Zeitlimit überschreitet (Prozess A, Transaktion 1: Sperren X). Dadurch wird die Sperre X entsperrt, wodurch Transaktion 2 usw. entsperrt wird.
Die Kunst besteht darin herauszufinden, was (welche Abfrage) die erste Sperre (Sperre X) verursacht. Sie können leicht sehen (
show engine innodb status
), dass Transaktion 3 auf Transaktion 2 wartet, aber Sie werden nicht sehen, auf welche Transaktion Transaktion 2 wartet (Transaktion 1). MySQL druckt keine mit Transaktion 1 verbundenen Sperren oder Abfragen. Der einzige Hinweis ist, dass ganz unten in der Transaktionsliste (dershow engine innodb status
Ausdrucks) Transaktion 1 anscheinend nichts tut (sondern tatsächlich auf Transaktion 3 wartet) Fertig).Hier wird beschrieben, wie ermittelt wird, durch welche SQL-Abfrage die Sperre (Lock X) für eine bestimmte wartende Transaktion gewährt wird
Tracking MySQL query history in long running transactions
Wenn Sie sich fragen, was der Prozess und die Transaktion genau im Beispiel sind. Der Prozess ist ein PHP-Prozess. Transaktion ist eine Transaktion im Sinne von innodb-trx-table . In meinem Fall hatte ich zwei PHP-Prozesse, in denen ich jeweils eine Transaktion manuell gestartet habe. Der interessante Teil war, dass MySQL, obwohl ich eine Transaktion in einem Prozess gestartet habe, intern zwei separate Transaktionen verwendet hat (ich habe keine Ahnung warum, vielleicht kann es ein MySQL-Entwickler erklären).
MySQL verwaltet seine eigenen Transaktionen intern und hat (in meinem Fall) beschlossen, zwei Transaktionen zu verwenden, um alle SQL-Anforderungen zu verarbeiten, die vom PHP-Prozess stammen (Prozess A). Die Aussage, dass Transaktion 1 auf den Abschluss von Transaktion 3 wartet, ist eine interne MySQL-Sache. MySQL "wusste", dass Transaktion 1 und Transaktion 3 tatsächlich als Teil einer "Transaktions" -Anforderung (aus Prozess A) instanziiert wurden. Jetzt wurde die gesamte "Transaktion" blockiert, da Transaktion 3 (ein Teil von "Transaktion") blockiert wurde. Da "Transaktion" die Transaktion 1 nicht beenden konnte (auch ein Teil der "Transaktion"), wurde sie ebenfalls als nicht abgeschlossen markiert. Dies ist, was ich mit "Transaktion 1 wartet auf den Abschluss von Transaktion 3" gemeint habe.
quelle
Das große Problem bei dieser Ausnahme ist, dass sie in einer Testumgebung normalerweise nicht reproduzierbar ist und wir nicht in der Lage sind, den Status der Innodb-Engine auszuführen, wenn dies auf einem Produkt geschieht. In einem der Projekte habe ich den folgenden Code für diese Ausnahme in einen Catch-Block eingefügt. Das hat mir geholfen, den Motorstatus zu ermitteln, als die Ausnahme auftrat. Das hat sehr geholfen.
quelle
Schauen Sie sich die Manpage des
pt-deadlock-logger
Dienstprogramms an :Es extrahiert Informationen aus den
engine innodb status
oben genannten und kann auch verwendet werden, um eine zu erstellen,daemon
die alle 30 Sekunden ausgeführt wird.quelle
Hier ist, was ich letztendlich tun musste, um herauszufinden, welche "andere Abfrage" das Problem mit dem Sperrzeitlimit verursacht hat. Im Anwendungscode verfolgen wir alle ausstehenden Datenbankaufrufe in einem separaten Thread, der dieser Aufgabe gewidmet ist. Wenn ein DB-Aufruf länger als N Sekunden dauert (für uns sind es 30 Sekunden), protokollieren wir:
Mit oben konnten wir gleichzeitige Abfragen lokalisieren, die die Zeilen sperrten, die den Deadlock verursachten. In meinem Fall handelte es sich um Anweisungen,
INSERT ... SELECT
die im Gegensatz zu einfachen SELECTs die zugrunde liegenden Zeilen sperren. Sie können den Code dann neu organisieren oder eine andere Transaktionsisolation verwenden, z. B. "Nicht festgeschrieben".Viel Glück!
quelle
Aus Rolandos Antwort oben extrapoliert, blockieren diese Ihre Anfrage:
Wenn Sie Ihre Abfrage ausführen müssen und nicht warten können, bis die anderen ausgeführt werden, beenden Sie sie mit der MySQL-Thread-ID:
(aus MySQL heraus, natürlich nicht aus der Shell)
Sie müssen die Thread-IDs finden aus:
Befehl, und finden Sie heraus, welcher die Datenbank blockiert.
quelle
5341773
? Ich sehe nicht, was das eine von den anderen unterscheidet.Sie können verwenden:
Hier werden alle Verbindungen in MySQL und der aktuelle Verbindungsstatus sowie die ausgeführte Abfrage aufgelistet. Es gibt auch eine kürzere Variante,
show processlist;
die die abgeschnittene Abfrage sowie die Verbindungsstatistiken anzeigt.quelle
Wenn Sie JDBC verwenden, haben Sie die Option
includeInnodbStatusInDeadlockExceptions = true
https://dev.mysql.com/doc/connector-j/8.0/de/connector-j-reference-configuration-properties.html
quelle
Aktivieren Sie MySQL general.log (festplattenintensiv) und verwenden Sie mysql_analyse_general_log.pl , um lang laufende Transaktionen zu extrahieren, z.
Deaktivieren Sie danach general.log.
quelle