web-dev-qa-db-de.com

Wie debuggen Sie das Wartezeitlimit für Sperren auf MySQL?

In meinen Produktionsfehlerprotokollen sehe ich gelegentlich:

SQLSTATE [HY000]: Allgemeiner Fehler: 1205 Wartezeit für Sperren überschritten; Versuchen Neustart der Transaktion

Ich weiß, welche Abfrage gerade versucht, auf die Datenbank zuzugreifen, aber gibt es eine Möglichkeit, herauszufinden, welche Abfrage zu diesem Zeitpunkt die Sperre hatte?

226
Matt McCormick

Was das verschenkt, ist das Wort transaction. Die Anweisung macht deutlich, 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, die der Täter sind.

Von dort aus sollten Sie SHOW ENGINE INNODB STATUS\G ausführen können.

Sie sollten die betroffenen Tabellen sehen können

Sie erhalten alle Arten zusätzlicher Locking- und Mutex-Informationen.

Hier ist ein Beispiel von einem meiner Kunden:

mysql> show engine innodb status\G
*************************** 1. row ***************************
  Type: InnoDB
  Name:
Status:
=====================================
110514 19:44:14 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 4 seconds
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 9014315, signal count 7805377
Mutex spin waits 0, rounds 11487096053, OS waits 7756855
RW-shared spins 722142, OS waits 211221; RW-excl spins 787046, OS waits 39353
------------------------
LATEST FOREIGN KEY ERROR
------------------------
110507 21:41:35 Transaction:
TRANSACTION 0 606162814, ACTIVE 0 sec, process no 29956, OS thread id 1223895360 updating or deleting, thread declared inside InnoDB 499
mysql tables in use 1, locked 1
14 lock struct(s), heap size 3024, 8 row lock(s), undo log entries 1
MySQL thread id 3686635, query id 124164167 10.64.89.145 viget updating
DELETE FROM file WHERE file_id in ('6dbafa39-7f00-0001-51f2-412a450be5cc' )
Foreign key constraint fails for table `backoffice`.`attachment`:
,
  CONSTRAINT `attachment_ibfk_2` FOREIGN KEY (`file_id`) REFERENCES `file` (`file_id`)
Trying to delete or update in parent table, in index `PRIMARY` Tuple:
DATA Tuple: 17 fields;
 0: len 36; hex 36646261666133392d376630302d303030312d353166322d343132613435306265356363; asc 6dbafa39-7f00-0001-51f2-412a450be5cc;; 1: len 6; hex 000024214f7e; asc   $!O~;; 2: len 7; hex 000000400217bc; asc    @   ;; 3: len 2; hex 03e9; asc   ;; 4: len 2; hex 03e8; asc   ;; 5: len 36; hex 65666635323863622d376630302d303030312d336632662d353239626433653361333032; asc eff528cb-7f00-0001-3f2f-529bd3e3a302;; 6: len 40; hex 36646234376337652d376630302d303030312d353166322d3431326132346664656366352e6d7033; asc 6db47c7e-7f00-0001-51f2-412a24fdecf5.mp3;; 7: len 21; hex 416e67656c73204e6f7720436f6e666572656e6365; asc Angels Now Conference;; 8: len 34; hex 416e67656c73204e6f7720436f6e666572656e6365204a756c7920392c2032303131; asc Angels Now Conference July 9, 2011;; 9: len 1; hex 80; asc  ;; 10: len 8; hex 8000124a5262bdf4; asc    JRb  ;; 11: len 8; hex 8000124a57669dc3; asc    JWf  ;; 12: SQL NULL; 13: len 5; hex 8000012200; asc    " ;; 14: len 1; hex 80; asc  ;; 15: len 2; hex 83e8; asc   ;; 16: len 4; hex 8000000a; asc     ;;

But in child table `backoffice`.`attachment`, in index `PRIMARY`, there is a record:
PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 30; hex 36646261666133392d376630302d303030312d353166322d343132613435; asc 6dbafa39-7f00-0001-51f2-412a45;...(truncated); 1: len 30; hex 38666164663561652d376630302d303030312d326436612d636164326361; asc 8fadf5ae-7f00-0001-2d6a-cad2ca;...(truncated); 2: len 6; hex 00002297b3ff; asc   "   ;; 3: len 7; hex 80000040070110; asc    @   ;; 4: len 2; hex 0000; asc   ;; 5: len 30; hex 416e67656c73204e6f7720436f6e666572656e636520446f63756d656e74; asc Angels Now Conference Document;;

------------
TRANSACTIONS
------------
Trx id counter 0 620783814
Purge done for trx's n:o < 0 620783800 undo n:o < 0 0
History list length 35
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 0 0, not started, process no 29956, OS thread id 1192212800
MySQL thread id 5341758, query id 189708501 127.0.0.1 lwdba
show innodb status
---TRANSACTION 0 620783788, not started, process no 29956, OS thread id 1196472640
MySQL thread id 5341773, query id 189708353 10.64.89.143 viget
---TRANSACTION 0 0, not started, process no 29956, OS thread id 1223895360
MySQL thread id 5341667, query id 189706152 10.64.89.145 viget
---TRANSACTION 0 0, not started, process no 29956, OS thread id 1227888960
MySQL thread id 5341556, query id 189699857 172.16.135.63 lwdba
---TRANSACTION 0 620781112, not started, process no 29956, OS thread id 1222297920
MySQL thread id 5341511, query id 189696265 10.64.89.143 viget
---TRANSACTION 0 620783736, not started, process no 29956, OS thread id 1229752640
MySQL thread id 5339005, query id 189707998 10.64.89.144 viget
---TRANSACTION 0 620783785, not started, process no 29956, OS thread id 1198602560
MySQL thread id 5337583, query id 189708349 10.64.89.145 viget
---TRANSACTION 0 620783469, not started, process no 29956, OS thread id 1224161600
MySQL thread id 5333500, query id 189708478 10.64.89.144 viget
---TRANSACTION 0 620781240, not started, process no 29956, OS thread id 1198336320
MySQL thread id 5324256, query id 189708493 10.64.89.145 viget
---TRANSACTION 0 617458223, not started, process no 29956, OS thread id 1195141440
MySQL thread id 736, query id 175038790 Has read all relay log; waiting for the slave I/O thread to update it
--------
FILE I/O
--------
I/O thread 0 state: waiting for i/o request (insert buffer thread)
I/O thread 1 state: waiting for i/o request (log thread)
I/O thread 2 state: waiting for i/o request (read thread)
I/O thread 3 state: waiting for i/o request (write thread)
Pending normal aio reads: 0, aio writes: 0,
 ibuf aio reads: 0, log i/o's: 0, sync i/o's: 0
Pending flushes (fsync) log: 0; buffer pool: 0
519878 OS file reads, 18962880 OS file writes, 13349046 OS fsyncs
0.00 reads/s, 0 avg bytes/read, 6.25 writes/s, 4.50 fsyncs/s
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 1190, seg size 1192,
174800 inserts, 174800 merged recs, 54439 merges
Hash table size 35401603, node heap has 35160 buffer(s)
0.50 hash searches/s, 11.75 non-hash searches/s
---
LOG
---
Log sequence number 28 1235093534
Log flushed up to   28 1235093534
Last checkpoint at  28 1235091275
0 pending log writes, 0 pending chkp writes
12262564 log i/o's done, 3.25 log i/o's/second
----------------------
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 18909316674; in additional pool allocated 1048576
Dictionary memory allocated 2019632
Buffer pool size   1048576
Free buffers       175763
Database pages     837653
Modified db pages  6
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages read 770138, created 108485, written 7795318
0.00 reads/s, 0.00 creates/s, 4.25 writes/s
Buffer pool hit rate 1000 / 1000
--------------
ROW OPERATIONS
--------------
0 queries inside InnoDB, 0 queries in queue
1 read views open inside InnoDB
Main thread process no. 29956, id 1185823040, state: sleeping
Number of rows inserted 6453767, updated 4602534, deleted 3638793, read 388349505551
0.25 inserts/s, 1.25 updates/s, 0.00 deletes/s, 2.75 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================

1 row in set, 1 warning (0.00 sec)

Sie sollten den Wert für das Wartezeitlimit für die Wartezeit für InnoDB erhöhen, indem Sie innodb_lock_wait_timeout festlegen. Der Standardwert ist 50 Sekunden

mysql> show variables like 'innodb_lock_wait_timeout';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 50    |
+--------------------------+-------+
1 row in set (0.01 sec)

Sie können es in /etc/my.cnf dauerhaft mit dieser Zeile auf einen höheren Wert setzen

[mysqld]
innodb_lock_wait_timeout=120

und starte mysql neu. Wenn Sie mysql zu diesem Zeitpunkt nicht neu starten können, führen Sie Folgendes aus:

SET GLOBAL innodb_lock_wait_timeout = 120; 

Sie können es auch einfach für die Dauer Ihrer Sitzung einstellen

SET innodb_lock_wait_timeout = 120; 

gefolgt von Ihrer Anfrage

232
RolandoMySQLDBA

Wie jemand in einem der vielen SO - Threads zu diesem Problem erwähnt hat: Manchmal erscheint der Prozess, der die Tabelle gesperrt hat, als in der Prozessliste liegend! Ich riss mir die Haare aus, bis ich alle schlafenden Fäden getötet habe, die in der betreffenden Datenbank geöffnet waren (zu diesem Zeitpunkt war noch keiner aktiv). Damit wurde die Tabelle endgültig entsperrt und die Aktualisierungsabfrage ausgeführt. 

Der Kommentator sagte etwas Ähnliches "Manchmal blockiert ein MySQL-Thread eine Tabelle und schläft dann, während er darauf wartet, dass etwas geschieht, das nicht zu MySQL gehört."

Nachdem ich das show engine innodb status-Protokoll erneut überprüft hatte (nachdem ich den für die Sperre verantwortlichen Client aufgespürt hatte), bemerkte ich, dass der fragliche blockierte Thread ganz unten in der Transaktionsliste unter den aktiven Abfragen aufgeführt war, die kurz davor waren Fehler wegen der eingefrorenen Sperre:

------------------
---TRANSACTION 2744943820, ACTIVE 1154 sec(!!)
2 lock struct(s), heap size 376, 2 row lock(s), undo log entries 1
MySQL thread id 276558, OS thread handle 0x7f93762e7710, query id 59264109 [ip] [database] cleaning up
Trx read view will not see trx with id >= 2744943821, sees < 2744943821

(nicht sicher, ob die Meldung "Trx-Leseansicht" mit der eingefrorenen Sperre zusammenhängt, aber im Gegensatz zu den anderen aktiven Transaktionen wird diese nicht mit der Abfrage angezeigt, die ausgegeben wurde, sondern behauptet, dass die Transaktion "aufgeräumt" ist, jedoch mehrere hat Zeilensperren)

Die Moral der Geschichte ist, dass eine Transaktion aktiv sein kann, obwohl der Thread schläft.

71
Eric Lawler

Aufgrund der Popularität von MySQL ist es kein Wunder, dass Wartezeit für Sperren überschritten wurde; versuchen Sie, die Transaktion neu zu starten Ausnahme erhält so viel Aufmerksamkeit auf SO.

Je mehr Konflikte Sie haben, desto größer ist die Wahrscheinlichkeit von Deadlocks, die eine DB-Engine durch das Timeout einer der Deadlock-Transaktionen löst. Außerdem können Transaktionen mit langer Laufzeit eine große Anzahl von Einträgen ändern (z. B. UPDATE oder DELETE) (wobei Sperren erforderlich sind, um Anomalien durch unsauberes Schreiben zu vermeiden, wie im Abschnitt Hoch- Performance Java Persistence book) führt eher zu Konflikten mit anderen Transaktionen.

Obwohl InnoDB MVCC, können Sie explizite Sperren mit der FOR UPDATE - Klausel anfordern. Im Gegensatz zu anderen gängigen DBs (Oracle, MSSQL, PostgreSQL, DB2) verwendet MySQL verwendet REPEATABLE_READ Als Standardisolationsstufe .

Jetzt werden die Sperren, die Sie erhalten haben (entweder durch Ändern von Zeilen oder durch explizites Sperren), für die Dauer der aktuell ausgeführten Transaktion gespeichert. Wenn Sie eine gute Erklärung für den Unterschied zwischen REPEATABLE_READ Und READ COMMITTED In Bezug auf das Sperren wünschen, lesen Sie diesen Percona-Artikel .

In REPEATABLE READ wird jede während einer Transaktion erworbene Sperre für die Dauer der Transaktion gehalten.

In READ COMMITTED werden die Sperren, die nicht mit dem Scan übereinstimmen, freigegeben, nachdem die Anweisung abgeschlossen wurde.

...

Dies bedeutet, dass in READ COMMITTED andere Transaktionen Zeilen aktualisieren können, die sie nach Abschluss der UPDATE-Anweisung nicht hätten aktualisieren können (in REPEATABLE READ).

Daher gilt: Je restriktiver die Isolationsstufe (REPEATABLE_READ, SERIALIZABLE) ist, desto größer ist die Wahrscheinlichkeit eines Deadlocks. Dies ist kein Problem "per se", es ist ein Kompromiss.

Sie können mit READ_COMMITED Sehr gute Ergebnisse erzielen, da Sie Verhinderung verlorener Aktualisierungen auf Anwendungsebene benötigen, wenn Sie logische Transaktionen verwenden, die sich über mehrere HTTP-Anforderungen erstrecken. Die optimistische Sperre Annäherung an Ziele verlorene Aktualisierungen kann auch dann auftreten, wenn Sie die Isolationsstufe SERIALIZABLE verwenden, während Sie den Sperrenkonflikt um reduzieren Damit können Sie READ_COMMITED verwenden.

40
Vlad Mihalcea

Für den Datensatz tritt die Ausnahme für das Wartezeitlimit für Sperren auch auf, wenn ein Deadlock vorliegt und MySQL es nicht erkennen kann. Ein anderer Grund könnte eine extrem lange laufende Abfrage sein, die jedoch einfacher zu lösen/reparieren ist, und ich möchte diesen Fall hier nicht beschreiben.

MySQL kann in der Regel mit Deadlocks umgehen, wenn sie innerhalb von zwei Transaktionen "richtig" erstellt werden. MySQL tötet dann nur die eine Transaktion, die weniger Sperren besitzt (ist weniger wichtig, da weniger Zeilen betroffen sind) und lässt die andere Transaktion beenden.

Nehmen wir an, es gibt zwei Prozesse A und B sowie drei Transaktionen:

Process A Transaction 1: Locks X
Process B Transaction 2: Locks Y
Process A Transaction 3: Needs Y => Waits for Y
Process B Transaction 2: Needs X => Waits for X
Process A Transaction 1: Waits for Transaction 3 to finish

(see the last two paragraph below to specify the terms in more detail)

=> deadlock 

Dies ist ein sehr unglückliches Setup, da MySQL keinen Deadlock erkennt (innerhalb von 3 Transaktionen). MySQL macht also ... nichts! Es wartet nur, da es nicht weiß, was zu tun ist. Es wartet, bis die erste erfasste Sperre das Zeitlimit überschreitet (Prozess A Transaktion 1: Sperren X). Dann wird die Sperre X freigegeben, wodurch Transaktion 2 usw. entsperrt wird.

Die Kunst besteht darin, herauszufinden, was (welche Abfrage) die erste Sperre verursacht (X sperren). Sie können leicht sehen (show engine innodb status), dass Transaktion 3 auf Transaktion 2 wartet, Sie werden jedoch nicht sehen, auf welche Transaktion Transaktion 2 wartet (Transaktion 1). MySQL druckt keine mit Transaction 1 verknüpften Sperren oder Abfragen. Der einzige Hinweis ist, dass am Ende der Transaktionsliste (des Ausdrucks show engine innodb status) Transaction 1 scheinbar nichts tut (tatsächlich jedoch auf Transaction 3 wartet) beenden).

Die Methode, um herauszufinden, welche SQL-Abfrage bewirkt, dass die Sperre (Lock X) für eine bestimmte Transaktion, die auf eine Transaktion wartet, gewährt wird, wird hier beschrieben. Tracking MySQL query history in long running transactions

Wenn Sie sich fragen, was der Prozess und die Transaktion genau in dem Beispiel ist. 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 jedem Fall habe ich eine Transaktion manuell gestartet. Der interessante Teil war, dass MySQL, obwohl ich eine Transaktion in einem Prozess gestartet hatte, intern zwei separate Transaktionen verwendete (ich habe keine Ahnung, warum einige MySQL-Entwickler vielleicht erklären können). 

MySQL verwaltet seine eigenen Transaktionen intern und beschließt (in meinem Fall), zwei Transaktionen zu verwenden, um alle SQL-Anforderungen zu verarbeiten, die vom Prozess PHP (Prozess A) stammen. Die Aussage, dass Transaction 1 auf Transaction 3 wartet, ist eine interne Sache von MySQL. MySQL "wusste", dass Transaktion 1 und Transaktion 3 als Teil einer "Transaktionsanforderung" (aus Prozess A) tatsächlich instanziiert wurden. Nun wurde die gesamte "Transaktion" gesperrt, weil die Transaktion 3 (ein Teil der "Transaktion") blockiert wurde. Da "Transaktion" die Transaktion 1 nicht abschließen konnte (auch ein Teil der "Transaktion"), wurde sie ebenfalls als nicht abgeschlossen markiert. Ich meinte dies mit "Transaktion 1 wartet, bis Transaktion 3 abgeschlossen ist".

18
Tomas Bilka

Das große Problem bei dieser Ausnahme ist, dass es normalerweise in einer Testumgebung nicht reproduzierbar ist und wir nicht in der Lage sind, den innodb-Engine-Status auszuführen, wenn dies auf prod geschieht. Also habe ich in einem der Projekte den folgenden Code für diese Ausnahme in einen catch-Block eingefügt. Das hat mir geholfen, den Motorstatus zu erfassen, als die Ausnahme passierte. Das hat sehr geholfen.

Statement st = con.createStatement();
ResultSet rs =  st.executeQuery("SHOW ENGINE INNODB STATUS");
while(rs.next()){
    log.info(rs.getString(1));
    log.info(rs.getString(2));
    log.info(rs.getString(3));
}
11
Maruthi

Werfen Sie einen Blick auf die Manpage des Dienstprogramms pt-deadlock-logger :

brew install percona-toolkit
pt-deadlock-logger --ask-pass server_name

Es extrahiert Informationen aus dem oben erwähnten engine innodb status und außerdem Es kann zum Erstellen einer daemon verwendet werden, die alle 30 Sekunden ausgeführt wird.

9
Andrei Sura

Aus der oben genannten Antwort von Rolando wird die folgende Abfrage Ihre Anfrage blockieren:

---TRANSACTION 0 620783788, not started, process no 29956, OS thread id 1196472640
MySQL thread id 5341773, query id 189708353 10.64.89.143 viget

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:

kill 5341773

(aus MySQL, nicht aus der Shell, offensichtlich)

Sie müssen die Thread-IDs finden von:

show engine innodb status\G

befehl und finden Sie heraus, welche die Datenbank blockiert.

7

Sie können verwenden:

show full processlist

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.

6
Gerrit Brink

Hier ist das, was ich letztendlich tun musste, um herauszufinden, welche "andere Abfrage" das Sperrzeitlimitproblem verursacht hat. Im Anwendungscode verfolgen wir alle ausstehenden Datenbankaufrufe in einem separaten Thread, der für diese Aufgabe bestimmt ist. Wenn ein DB-Aufruf länger als N Sekunden dauert (für uns sind es 30 Sekunden), protokollieren wir:

// Pending InnoDB transactions
SELECT * FROM information_schema.innodb_trx ORDER BY trx_started; 

// Optionally, log what transaction holds what locks
SELECT * FROM information_schema.innodb_locks;

Mit oben konnten wir gleichzeitige Abfragen ermitteln, die die Zeilen blockierten, die den Deadlock verursachten. In meinem Fall waren dies Anweisungen wie INSERT ... SELECT, die im Gegensatz zu einfachen SELECTs die darunter liegenden Zeilen sperren. Sie können den Code anschließend neu organisieren oder eine andere Transaktionsisolation verwenden, z. B. Read Commit.

Viel Glück!

2
Slawomir

Wenn Sie JDBC verwenden, haben Sie die Option
includeInnodbStatusInDeadlockExceptions = true

https://dev.mysql.com/doc/connector-j/8.0/de/connector-j-reference-configuration-immobilien.html

0
th3sly