web-dev-qa-db-fra.com

Comment déboguer le délai d'attente de verrouillage dépassé sur MySQL?

Dans mes journaux d'erreurs de production, je vois parfois:

SQLSTATE [HY000]: Erreur générale: 1205 Délai d'attente de verrouillage dépassé; essayer redémarrage de la transaction

Je sais quelle requête tente d'accéder à la base de données à ce moment-là, mais existe-t-il un moyen de savoir quelle requête avait le verrou à ce moment précis?

226
Matt McCormick

Ce qui donne ceci est le Word transaction. Il est évident d'après la déclaration que la requête a tenté de modifier au moins une ligne dans une ou plusieurs tables InnoDB.

Puisque vous connaissez la requête, toutes les tables auxquelles vous accédez sont des candidats potentiels.

À partir de là, vous devriez pouvoir exécuter SHOW ENGINE INNODB STATUS\G

Vous devriez pouvoir voir la (les) table (s) affectée (s)

Vous obtenez toutes sortes d'informations supplémentaires sur le verrouillage et le mutex.

Voici un échantillon d'un de mes clients:

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)

Vous devriez envisager d'augmenter la valeur du délai d'attente de verrouillage pour InnoDB en définissant le paramètre innodb_lock_wait_timeout , la valeur par défaut est 50 s

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

Vous pouvez lui attribuer une valeur supérieure dans /etc/my.cnf de manière permanente avec cette ligne.

[mysqld]
innodb_lock_wait_timeout=120

et redémarrez mysql. Si vous ne pouvez pas redémarrer mysql à ce stade, lancez ceci:

SET GLOBAL innodb_lock_wait_timeout = 120; 

Vous pouvez également simplement le définir pour la durée de votre session

SET innodb_lock_wait_timeout = 120; 

suivi de votre requête

232
RolandoMySQLDBA

Comme l’a mentionné une des nombreuses SO discussions concernant ce problème: Parfois, le processus qui a verrouillé la table s’affiche en sommeil dans la liste des processus! J'étais en train de m'arracher les cheveux jusqu'à ce que je tue tous les fils endormis qui étaient ouverts dans la base de données en question (aucun n'était actif à ce moment-là). Cela a finalement déverrouillé la table et laissé la requête de mise à jour s'exécuter. 

Le commentateur a dit quelque chose qui ressemble à "Parfois un thread MySQL verrouille une table, puis dort en attendant qu'il se passe quelque chose de non lié à MySQL."

Après avoir réexaminé le journal show engine innodb status (une fois que j'avais repéré le client responsable du verrou), j'ai constaté que le fil bloqué en question était répertorié tout en bas de la liste des transactions, sous les requêtes actives sur le point de l'être. erreur sur en raison du verrou gelé:

------------------
---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

(incertain si le message "Trx read view" est lié au verrou gelé, mais contrairement aux autres transactions actives, celle-ci ne s'affiche pas avec la requête qui a été émise et prétend à la place que la transaction est en train de "nettoyer," a encore rangées)

La morale de l'histoire est qu'une transaction peut être active même si le fil est en veille.

71
Eric Lawler

En raison de la popularité de MySQL, il n’ya rien d’étonnant à ce que le délai d’attente du verrouillage soit dépassé; essayez de redémarrer transaction exception attire tellement l'attention sur SO.

Plus le nombre de conflits est élevé, plus le blocage des blocages est susceptible d'être résolu par un moteur de base de données en retardant l'exécution d'une des transactions bloquées. De même, les transactions de longue durée ayant modifié (par exemple, UPDATE ou DELETE) un grand nombre d'entrées (verrouillables pour éviter les anomalies d'écriture incorrecte, comme expliqué dans le Persistance Java hautes performances book) sont plus susceptibles de générer conflits avec d'autres transactions.

Bien que InnoDB MVCC, vous pouvez toujours demander des verrous explicites en utilisant la clause FOR UPDATE . Cependant, contrairement aux autres bases de données populaires (Oracle, MSSQL, PostgreSQL, DB2), MySQL utilise REPEATABLE_READ comme niveau d’isolement par défaut

Désormais, les verrous que vous avez acquis (soit en modifiant des lignes, soit en utilisant un verrouillage explicite) sont conservés pendant la durée de la transaction en cours d'exécution. Si vous voulez une bonne explication de la différence entre REPEATABLE_READ et READ COMMITTED en ce qui concerne le verrouillage, merci de lire cet article de Percona .

Dans REPEATABLE READ, chaque verrou acquis lors d’une transaction est maintenu pour la durée de la transaction.

Dans READ COMMITTED, les verrous qui ne correspondaient pas à l'analyse sont libérés à la fin de la commande STATEMENT.

...

Cela signifie que dans READ COMMITTED, les autres transactions sont libres de mettre à jour des lignes qu'elles n'auraient pas été en mesure de mettre à jour (dans REPEATABLE READ) une fois l'instruction UPDATE terminée.

Plus le niveau d’isolation (REPEATABLE_READ, SERIALIZABLE) est restrictif, plus le risque de blocage est élevé. Ce n'est pas un problème "en soi", c'est un compromis.

Vous pouvez obtenir de très bons résultats avec READ_COMMITED, car vous avez besoin prévention de la perte de mises à jour au niveau de l'application lorsque vous utilisez des transactions logiques couvrant plusieurs requêtes HTTP. Le verrouillage optimiste approche les cibles mises à jour perdues qui peuvent se produire même si vous utilisez le niveau d’isolement SERIALIZABLE tout en réduisant le conflit de verrouillage en vous permettant d’utiliser READ_COMMITED.

31
Vlad Mihalcea

Pour mémoire, l'exception de délai d'attente de verrouillage se produit également lorsqu'il y a un blocage et que MySQL ne peut pas le détecter. Par conséquent, le délai est dépassé. Une autre raison pourrait être une requête extrêmement longue, qui est plus facile à résoudre/réparer, cependant, et je ne décrirai pas ce cas ici.

MySQL est généralement capable de gérer les blocages s’ils sont construits «correctement» au cours de deux transactions. Ensuite, MySQL supprime/annule simplement la transaction qui possède moins de verrous (est moins importante car elle affectera moins de lignes) et laissera l’autre finir.

Supposons maintenant qu'il existe deux processus A et B et trois transactions:

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 

C'est une configuration très malheureuse car MySQL ne peut pas voir qu'il y a un blocage (couvert dans 3 transactions). Donc, ce que MySQL fait, c'est ... rien! Il attend juste, car il ne sait pas quoi faire. Il attend que le premier verrou acquis dépasse le délai imparti (processus A, transaction 1: verrous X), puis déverrouille le verrou X, ce qui déverrouille la transaction 2, etc.

L'art consiste à découvrir ce qui (quelle requête) provoque le premier verrou (Lock X). Vous pourrez facilement voir (show engine innodb status) que la transaction 3 attend la transaction 2, mais vous ne verrez pas quelle transaction attend la transaction 2 (transaction 1). MySQL n’imprimera aucun verrou ni aucune requête associée à Transaction 1. Le seul indice est qu’au bas de la liste des transactions (de l’impression show engine innodb status), vous verrez que la transaction 1 ne fait apparemment rien (mais attend en fait la transaction 3). pour finir).

La technique permettant de déterminer quelle requête SQL provoque l'octroi du verrou (verrou X) pour une transaction donnée en attente est décrite ici Tracking MySQL query history in long running transactions

Si vous vous demandez quel est le processus et la transaction exactement dans l'exemple. Le processus est un processus PHP. Transaction est une transaction définie par innodb-trx-table . Dans mon cas, j’avais deux processus PHP, dans lesquels j’ai démarré une transaction manuellement. La partie intéressante est que même si j'ai démarré une transaction dans un processus, MySQL a utilisé en interne deux transactions distinctes (je ne sais pas pourquoi, peut-être qu'un développeur MySQL peut expliquer). 

MySQL gère ses propres transactions en interne et a décidé (dans mon cas) d'utiliser deux transactions pour gérer toutes les requêtes SQL provenant du processus PHP (processus A). L'instruction selon laquelle Transaction 1 attend la fin de Transaction 3 est un problème interne à MySQL. MySQL "savait" que les transactions 1 et 3 avaient été instanciées dans le cadre d'une requête "transaction" (à partir du processus A). Maintenant, toute la "transaction" a été bloquée parce que la transaction 3 (une sous-partie de "transaction") a été bloquée. Parce que "transaction" n'a pas été en mesure de terminer la transaction 1 (également une sous-partie de la "transaction") a été marquée comme non terminée également. C'est ce que je voulais dire par "La transaction 1 attend la fin de la transaction 3".

18
Tomas Bilka

Le gros problème avec cette exception est qu’elle n’est généralement pas reproductible dans un environnement de test et que nous n’avons pas la possibilité d’exécuter le statut du moteur innodb lorsque cela se produit lorsqu’il se produit. Donc, dans l'un des projets, j'ai mis le code ci-dessous dans un bloc catch pour cette exception. Cela m'a aidé à connaître l'état du moteur lorsque l'exception s'est produite. Cela a beaucoup aidé.

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

Jetez un coup d’œil à la page de manuel de l’utilitaire pt-deadlock-logger :

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

Il extrait les informations du engine innodb status mentionné ci-dessus ainsi que Il peut être utilisé pour créer une daemon qui s'exécute toutes les 30 secondes.

9
Andrei Sura

Extrapolant à partir de la réponse de Rolando ci-dessus, ce sont ces derniers qui bloquent votre requête:

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

Si vous avez besoin d'exécuter votre requête et que vous ne pouvez pas attendre que les autres s'exécutent, éliminez-les à l'aide de l'ID de thread MySQL:

kill 5341773

(de mysql, pas du shell, évidemment)

Vous devez trouver les ID de threads à partir du:

show engine innodb status\G

commande, et déterminer lequel est celui qui bloque la base de données.

7
Ellert van Koperen

Vous pouvez utiliser:

show full processlist

qui listera toutes les connexions dans MySQL et l’état actuel de connexion ainsi que la requête en cours d’exécution Il existe également une variante plus courte show processlist; qui affiche la requête tronquée ainsi que les statistiques de connexion.

6
Gerrit Brink

Voici ce que j'ai finalement dû faire pour déterminer quelle "autre requête" était à l'origine du problème de délai de verrouillage. Dans le code de l'application, nous suivons tous les appels de base de données en attente sur un thread distinct dédié à cette tâche. Si un appel de base de données dure plus de N secondes (pour nous, 30 secondes), nous enregistrons:

// 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;

Avec ce qui précède, nous avons pu identifier des requêtes simultanées qui ont verrouillé les lignes provoquant l’impasse. Dans mon cas, c'étaient des instructions comme INSERT ... SELECT qui, contrairement aux SELECT simples, verrouillent les lignes sous-jacentes. Vous pouvez ensuite réorganiser le code ou utiliser une isolation de transaction différente, telle que lecture non validée.

Bonne chance!

2
Slawomir

Si vous utilisez JDBC, vous avez l'option
includeInnodbStatusInDeadlockExceptions = true

https://dev.mysql.com/doc/connector-j/8.0/fr/connector-j-reference-configuration-properties.html

0
th3sly