Mysql
 sql >> Datenbank >  >> RDS >> Mysql

MySQL:Ständig Waiting for table metadata lock

Die akzeptierte Lösung ist leider falsch . Es ist richtig, soweit es sagt,

Das ist in der Tat (fast sicherlich; siehe unten) was zu tun ist. Aber dann schlägt es vor,

...und 1398 nicht die Verbindung mit dem Schloss. Wie könnte es sein? 1398 ist die Verbindung wartend für das Schloss. Das bedeutet, es hat noch nicht das Schloss, und daher nützt es nichts, es zu töten. Der Prozess, der die Sperre hält, wird die Sperre weiterhin halten, und der nächste Thread, der versucht, etwas zu tun, wird daher auch stall und geben Sie in der richtigen Reihenfolge "Warten auf Metadatensperre" ein.

Sie haben keine Garantie dafür, dass die Prozesse, die auf Metadatensperre warten (WFML), nicht auch blockieren, aber Sie können sicher sein, dass das Beenden von nur WFML-Prozessen genau nichts bewirkt .

Die wahre Ursache ist, dass ein anderer Prozess die Sperre hält , und noch wichtiger, SHOW FULL PROCESSLIST wird Ihnen nicht direkt sagen, was es ist .

Es wird WERDEN sagen Ihnen, ob der Prozess läuft etwas, ja. Normalerweise funktioniert es. Hier macht der Prozess, der die Sperre hält, nichts , und versteckt sich unter anderen Threads, die auch nichts tun.

In diesem Fall ist der Schuldige fast sicher Prozess 1396 , der vor Prozess 1398 gestartet wurde und sich jetzt im Sleep befindet Zustand, und zwar seit 46 Sekunden. Since 1396 hat eindeutig alles getan, was es tun musste (was durch die Tatsache bewiesen wird, dass es jetzt schläft, und dies für 46 Sekunden getan hat, was MySQL betrifft ), kein Thread, der zuvor schlafen gegangen wäre, hätte eine Sperre halten können (oder 1396 wäre auch hängen geblieben).

WICHTIG :Wenn Sie sich als eingeschränkter Benutzer mit MySQL verbunden haben, SHOW FULL PROCESSLIST wird nicht alle Prozesse zeigen. Die Sperre könnte also von einem Prozess gehalten werden, den Sie nicht sehen.

Eine bessere SHOW PROCESSLIST

SELECT ID, TIME, USER, HOST, DB, COMMAND, STATE, INFO
    FROM INFORMATION_SCHEMA.PROCESSLIST WHERE DB IS NOT NULL
    AND (`INFO` NOT LIKE '%INFORMATION_SCHEMA%' OR INFO IS NULL)
    ORDER BY `DB`, `TIME` DESC

Das Obige kann so eingestellt werden, dass nur die Prozesse im SLEEP-Zustand angezeigt werden, und es sortiert sie trotzdem nach absteigender Zeit, sodass es einfacher ist, den hängenden Prozess zu finden (normalerweise ist es der Sleep 'eins unmittelbar vor denen "Warten auf Metadatensperre").

Das Wichtigste

Lassen Sie alle "Warten auf Metadatensperre"-Prozesse in Ruhe .

Schnelle und schmutzige Lösung, nicht wirklich empfehlenswert, aber schnell

Töte alle Prozesse im Ruhezustand in derselben Datenbank, die älter als die älteste sind Thread im Status "Warten auf Metadatensperre". Das ist Arnaud Amaury hätte getan:

  • für jede Datenbank, die mindestens einen Thread in WaitingForMetadataLock hat:
    • Die älteste Verbindung in WFML auf dieser Datenbank ist Z Sekunden alt
    • ALLE "Sleep"-Threads auf dieser DB und älter als Z müssen gehen. Beginnen Sie mit den frischesten, nur für den Fall.
    • Wenn eine ältere und nicht schlafende Verbindung auf dieser DB existiert, dann ist das vielleicht diejenige, die die Sperre hält, aber sie tut etwas . Sie können es natürlich löschen, aber insbesondere wenn es sich um ein UPDATE/INSERT/DELETE handelt, tun Sie dies auf eigene Gefahr.

In neunundneunzig von hundert Fällen ist der zu tötende Thread der jüngste unter denen im Schlafzustand, die älter sind als die ältere, die auf Metadatensperre wartet:

TIME     STATUS
319      Sleep
205      Sleep
 19      Sleep                      <--- one of these two "19"
 19      Sleep                      <--- and probably this one(*)
 15      Waiting for metadata lock  <--- oldest WFML
 15      Waiting for metadata lock
 14      Waiting for metadata lock

(*) Die TIME-Reihenfolge hat tatsächlich Millisekunden, oder so wurde mir gesagt, sie zeigt sie einfach nicht. Während also beide Prozesse einen Zeitwert von 19 haben, sollte der niedrigste jünger sein.

Fokussiertere Lösung

Führen Sie SHOW ENGINE INNODB STATUS aus und sehen Sie sich den Abschnitt "TRANSAKTION" an. Sie werden unter anderem so etwas wie

finden
TRANSACTION 1701, ACTIVE 58 sec;2 lock struct(s), heap size 376, 1 row lock(s), undo log entries 1
MySQL thread id 1396, OS thread handle 0x7fd06d675700, query id 1138 hostname 1.2.3.4 whatever;

Jetzt überprüfen Sie mit SHOW FULL PROCESSLIST Was macht Thread-ID 1396 mit seiner Transaktion Nr. 1701? Es besteht die Möglichkeit, dass es sich im „Schlaf“-Status befindet. Also:eine aktive Transaktion (#1701) mit einer aktiven Sperre, sie hat sogar einige Änderungen vorgenommen, da sie einen Undo-Log-Eintrag hat ... aber derzeit im Leerlauf ist. Das und kein anderer ist der Thread, den Sie töten müssen. Diese Änderungen gehen verloren.

Denken Sie daran, dass Nichtstun in MySQL nicht gleichbedeutend ist mit Nichtstun im Allgemeinen. Wenn Sie einige Datensätze von MySQL erhalten und eine CSV-Datei für den FTP-Upload erstellen, ist die MySQL-Verbindung während des FTP-Uploads im Leerlauf.

Wenn sich der Prozess, der MySQL verwendet, und der MySQL-Server auf derselben Maschine befinden, auf dieser Maschine Linux läuft und Sie über Root-Rechte verfügen, gibt es eine Möglichkeit, herauszufinden, welcher Prozess hat die Verbindung, die die Sperre angefordert hat. Dies wiederum erlaubt (aus der CPU-Auslastung oder schlimmstenfalls strace -ff -p pid ), ob dieser Prozess wirklich ist etwas zu tun oder nicht, um zu entscheiden, ob es sicher ist zu töten.

Warum passiert das?

Ich sehe dies bei Webapps, die "persistente" oder "gepoolte" MySQL-Verbindungen verwenden, die heutzutage normalerweise sehr wenig Zeit sparen:Die Webapp-Instanz wurde beendet, aber die Verbindung nicht , also ist seine Sperre noch am Leben ... und blockiert alle anderen.

Ein weiterer interessanter Weg Was ich in den obigen Hypothesen gefunden habe, besteht darin, eine Abfrage auszuführen, die einige Zeilen zurückgibt, und nur einige davon abruft . Wenn die Abfrage nicht auf "automatische Bereinigung" eingestellt ist (wie auch immer der zugrunde liegende DBA dies tut), hält sie die Verbindung offen und verhindert, dass eine vollständige Sperre für die Tabelle durchgeht. Mir ist das in einem Codestück passiert, das überprüft, ob eine Zeile existiert, indem ich diese Zeile auswähle und verifiziere, ob ein Fehler aufgetreten ist (nicht vorhanden) oder nicht (muss vorhanden sein), aber ohne die Zeile tatsächlich abzurufen .

Fragen Sie die Datenbank

Eine andere Möglichkeit, den Übeltäter zu finden, wenn Sie ein aktuelles MySQL haben, aber nicht zu neu denn dies wird veraltet sein , ist (Sie benötigen wieder Berechtigungen für das Informationsschema)

SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS 
     WHERE LOCK_TRX_ID IN 
        (SELECT BLOCKING_TRX_ID FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS);

Eigentliche Lösung, die Zeit und Arbeit erfordert

Das Problem wird normalerweise durch diese Architektur verursacht:

Wenn die Webanwendung stirbt oder die leichte Thread-Instanz der Webanwendung stirbt, der Container/Verbindungspool möglicherweise nicht . Und es ist der Behälter das hält die Verbindung offen, also wird die Verbindung offensichtlich nicht geschlossen. Ganz vorhersehbar betrachtet MySQL den Vorgang nicht als abgeschlossen .

Wenn die Webapp nicht nach sich selbst bereinigt hat (kein ROLLBACK oder COMMIT für eine Transaktion kein UNLOCK TABLES , usw.), dann ist alles, was diese Webanwendung begonnen hat, immer noch vorhanden , und blockiert möglicherweise immer noch alle anderen.

Dann gibt es zwei Lösungen. Das Schlimmste ist, das Leerlauf-Timeout zu senken . Aber raten Sie mal, was passiert, wenn Sie zwischen zwei Abfragen zu lange warten (genau:"MySQL-Server ist weg"). Sie könnten dann mysql_ping verwenden falls verfügbar (bald veraltet. Es gibt Workarounds für PDO. Oder Sie könnten das überprüfen Fehler, und öffnen Sie die Verbindung erneut, wenn dies auftritt (dies ist der Python-Weg). Also - für eine kleine Leistungsgebühr - ist es machbar.

Die bessere, intelligentere Lösung ist weniger einfach zu implementieren. Bemühen Sie sich darum, dass das Skript nach sich selbst bereinigt wird, und stellen Sie sicher, dass alle Zeilen abgerufen oder alle Abfrageressourcen freigegeben werden, alle Ausnahmen abgefangen und ordnungsgemäß behandelt werden, oder, wenn möglich, persistente Verbindungen insgesamt überspringen . Lassen Sie jede Instanz ihre eigene Verbindung erstellen oder verwenden Sie eine intelligente Pool-Treiber (In PHP PDO verwenden Sie PDO::ATTR_PERSISTENT explizit auf false gesetzt ). Alternativ (z. B. in PHP) können Sie Destruct- und Exception-Handler dazu bringen, die Verbindung zu bereinigen, indem Sie Transaktionen festschreiben oder rückgängig machen und explizite Tabellenentsperrungen ausgeben.

Ich kenne keine Möglichkeit, nach vorhandenen Ergebnismengenressourcen zu fragen, um sie freizugeben. die einzige Möglichkeit wäre Speichern diese Ressourcen in einem privaten Array.