Auteur: huit étrange (Gao) également dans les experts en technologie de base de données
hu.com/p/d95bba14eddf
Comment trouver et tuer rapidement la session qui a provoqué le blocage des transactions.
Cet article se concentre sur MySQL 5.7.29 et ajoutera également une comparaison avec la version 8.0.
1. L'origine du problème
Dans le processus d'exploitation et de maintenance de MySQL, nous devons avoir rencontré plus ou moins des problèmes de verrouillage de ligne Innodb. Si nous les rencontrons en ligne, nous pouvons voir un gros bloc de sessions dans un état bloqué. Habituellement, nous verrons ce qui suit dans la liste des processus d'affichage:
L'instruction de sélection pour la mise à jour est à l'état d'envoi des données
L'instruction de mise à jour / suppression est à l'état de mise à jour
L'instruction d'insertion est à l'état de mise à jour
Alors, comment tuer rapidement la session bloquée lors de la rencontre de ce genre de problème, certaines personnes peuvent dire que vous pouvez faire tuer la session en vérifiant sys.innodb_lock_waits, mais si nous simulons au hasard les quatre transactions de A, B, C et D, B, C, D attendent tous le verrou de A en même temps (une transaction n'est pas validée), puis vérifiez sys.innodb_lock_waits et vous verrez l'instruction kill suivante:
mysql> select sql_kill_blocking_connection from sys.innodb_lock_waits ;
+------------------------------+
| sql_kill_blocking_connection |
+------------------------------+
| KILL 19 |
| KILL 18 |
| KILL 19 |
| KILL 14 |
| KILL 18 |
| KILL 19 |
+------------------------------+
6 rows in set (0.34 sec)
Alors, quelle session tuer est la session de la transaction A?
Avec ce problème, nous analysons, ce qui ajoute une entrée de code.
Deuxièmement, la source de sys.innodb_lock_waits
En fait, sys.innodb_lock_waits est une vue, et la source de 5.7.29 est la suivante:
SELECT r.trx_wait_started AS wait_started,
TIMEDIFF(NOW(), r.trx_wait_started) AS wait_age,
TIMESTAMPDIFF(SECOND, r.trx_wait_started, NOW()) AS wait_age_secs,
rl.lock_table AS locked_table,
rl.lock_index AS locked_index,
rl.lock_type AS locked_type,
r.trx_id AS waiting_trx_id,
r.trx_started as waiting_trx_started,
TIMEDIFF(NOW(), r.trx_started) AS waiting_trx_age,
r.trx_rows_locked AS waiting_trx_rows_locked,
r.trx_rows_modified AS waiting_trx_rows_modified,
r.trx_mysql_thread_id AS waiting_pid,
sys.format_statement(r.trx_query) AS waiting_query,
rl.lock_id AS waiting_lock_id,
rl.lock_mode AS waiting_lock_mode,
b.trx_id AS blocking_trx_id,
b.trx_mysql_thread_id AS blocking_pid,
sys.format_statement(b.trx_query) AS blocking_query,
bl.lock_id AS blocking_lock_id,
bl.lock_mode AS blocking_lock_mode,
b.trx_started AS blocking_trx_started,
TIMEDIFF(NOW(), b.trx_started) AS blocking_trx_age,
b.trx_rows_locked AS blocking_trx_rows_locked,
b.trx_rows_modified AS blocking_trx_rows_modified,
CONCAT('KILL QUERY ', b.trx_mysql_thread_id) AS sql_kill_blocking_query,
CONCAT('KILL ', b.trx_mysql_thread_id) AS sql_kill_blocking_connection
FROM information_schema.innodb_lock_waits w
INNER JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id
INNER JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id
INNER JOIN information_schema.innodb_locks bl ON bl.lock_id = w.blocking_lock_id
INNER JOIN information_schema.innodb_locks rl ON rl.lock_id = w.requested_lock_id
ORDER BY r.trx_wait_started;
Vous pouvez voir que sa source est en fait
information_schema.innodb_lock_waits
information_schema.innodb_trx
Les trois tables information_schema.innodb_locks.
Ensuite, nous devons déterminer quelles informations de transaction sont contenues dans les données de ces trois tableaux.
3. Sources d'information respectives
information_schema.innodb_trx: les données de cette table sont chargées à chaque fois qu'une requête est effectuée. Elle contient principalement toutes les sources d'informations de toutes les transactions en lecture-écriture et des transactions en lecture seule sous forme de trx_sys.rw_trx_list et trx_sys.mysql_trx_list. Vous pouvez voir comment il est appelé dans la fonction fetch_data_into_cache.
information_schema.innodb_locks: les données de cette table sont chargées à chaque fois qu'une requête est effectuée, notamment toutes les transactions de demande et les transactions de blocage sur une file d'attente de ressources de verrouillage de ligne qui sont actuellement en état d'attente. Une opération de déduplication se trouve dans add_lock_to_cache Fonction. Mais dans 8, il est appelé performance_schema.data_locks. Les informations contenues sont différentes. Il contiendra toutes les informations de verrouillage d'enregistrement qui ont été acquises et doivent être acquises, similaires aux informations de l'état innodb de show engine.
information_schema.innodb_lock_waits: les données de cette table sont chargées à chaque fois qu'une requête est exécutée et elles contiennent principalement toutes les informations de transaction sur cette file d'attente de ressources de verrouillage de ligne dans chaque transaction d'état d'attente. Il s'appelle performance_schema.data_lock_waits dans 8, et ses informations de base ne sont pas très différentes de 5.7.
En fait, cette file d'attente existe réellement dans la table de recherche de hachage du verrou de ligne, et les informations de file d'attente de la ressource de verrouillage d'attente de chaque transaction seront supprimées en fonction de la requête.
Son interne est un itérateur lock_queue_iterator_reset (& iter, trx-> lock.wait_lock, ULINT_UNDEFINED). Cet itérateur est utilisé pour itérer les informations de file d'attente dans la table de recherche de hachage de verrouillage de ligne. Le début de son itération est la ressource de verrouillage de la transaction actuellement en état d'attente (requête Le verrou de ligne de la transaction se trouve dans trx-> lock.wait_lock, et ce processus se trouve dans la fonction add_trx_relevant_locks_to_cache.
Deuxièmement, en 5.7.29, ces trois informations sont généralement chargées en même temps, elles se trouvent sous la fonction fetch_data_into_cache, comme suit
fetch_data_into_cache
-> fetch_data_into_cache_low(cache, true, &trx_sys->rw_trx_list)
本处循环每一个读写事务装载数据
->add_trx_relevant_locks_to_cache
本处循环迭代整个等待队列进行转载数据,包含information_schema.innodb_locks和information_schema.innodb_lock_waits
将读写事务信息装载入information_schema.innodb_trx中
-> fetch_data_into_cache_low(cache, false, &trx_sys->mysql_trx_list)
本处循环每一个事务装载数据,并且会跳过已经装载的读写事务
将只读事务信息装载入information_schema.innodb_trx中
Quatre, utilisez un diagramme pour illustrer
information_schema.innodb_trx: contient des informations sur les 5 transactions de A, B, C, D et E. Bien sûr, il n'y a pas de transaction en lecture seule ici. S'il y a une transaction en lecture seule, elle sera incluse. Dans le code, je peux voir le jugement de la transaction en lecture seule principalement via (! Rw_trx_list && trx-> id! = 0), et si le pass est dans Il est déterminé si la file d'attente de lecture-écriture et l'ID de transaction sont 0 (car la transaction en lecture seule ne reçoit pas d'ID de transaction de 0)
information_schema.innodb_locks: contient toutes les transactions dans la partie rouge de la figure, donc quatre transactions A, B, C et D sont incluses, mais elles n'apparaîtront qu'une seule fois car elles seront dédupliquées. Les informations de ce tableau sont différentes de 5.7 et 8 comme décrit ci-dessus
information_schema.innodb_lock_waits: contient toutes les transactions dans la partie rouge de la figure, et il y a des informations détaillées sur la file d'attente comme suit, il n'y a pas beaucoup de différence entre les tableaux 5.7 et 8.
Transaction D | Transaction C | Transaction B |
---|---|---|
attente de la transaction D-> blocage de la transaction C | attente de la transaction C-> blocage de la transaction B | attente de la transaction B-> blocage de la transaction A |
attente de la transaction D-> blocage de la transaction B | attente de la transaction C-> blocage de la transaction A | |
attente de la transaction D-> blocage de la transaction A |
Ensuite, il y aura 6 lignes d'informations, soit le même nombre de lignes que 5.7 et 8. 5.7.29 est le suivant:
8.0.18 est le suivant:
Ici, nous devons faire attention: dans ce cas, nous pouvons constater que l'ID de transaction avec le plus d'occurrences dans le blocking_trx_id de information_schema.innodb_lock_waits est susceptible d'être la source du blocage, et les informations dans sys.innodb_lock_waits proviennent de information_schema.innodb_lock_waits et des deux autres tables. La jointure est également de 6 lignes comme suit:
C'est juste que l'identifiant de processus de la transaction de blocage est trouvé via la connexion.
5. Comment éliminer rapidement la source possible de blocage
Maintenant que l'analyse précédente est très simple, nous pouvons utiliser la méthode suivante (pour 5.7 / 8.0):
1. Exécutez l'instruction pour trouver la session la plus bloquée
select trim(LEADING 'KILL ' from sql_kill_blocking_connection),count(*)
from sys.innodb_lock_waits
group by trim(LEADING 'KILL ' from sql_kill_blocking_connection) order by count(*) desc;
+---------------------------------------------------------+----------+
| trim(LEADING 'KILL ' from sql_kill_blocking_connection) | count(*) |
+---------------------------------------------------------+----------+
| 407 | 12 |
| 408 | 11 |
| 409 | 10 |
| 410 | 9 |
| 411 | 8 |
| 412 | 7 |
| 413 | 6 |
| 414 | 5 |
| 415 | 4 |
| 416 | 3 |
| 417 | 2 |
| 418 | 1 |
+---------------------------------------------------------+----------+
Le premier signifie que plus de sessions sont bloquées.
2. Recherchez les informations de transaction en cours du processus id 407
Observez son statut de transaction et l'instruction qui peut être exécutée ou l'instruction précédente pour déterminer si elle peut être supprimée.
select trx_id,trx_operation_state,trx_mysql_thread_id prs_id,now(),trx_started,
to_seconds(now())-to_seconds(trx_started) trx_es_time,
user,db,host,state,Time,info current_sql,PROCESSLIST_INFO last_sql
from information_schema.innodb_trx t1,information_schema.processlist t2,performance_schema.threads t3
where t1.trx_mysql_thread_id=t2.id
and t1.trx_mysql_thread_id=t3.PROCESSLIST_ID
and t1.trx_mysql_thread_id!=connection_id()
and t2.id=407;
Bien sûr, vous pouvez également enregistrer des informations d'état d'innodb de show engine pour une analyse ultérieure avant de tuer la session.
3. Bouclez ce processus, car il peut y avoir plusieurs ressources de verrouillage de ligne bloquées
Le texte intégral est terminé.
Profitez de MySQL :)
La classe "MySQL Core Optimization" du professeur Ye a été mise à niveau vers MySQL 8.0, scannez le code pour commencer le voyage de la pratique de MySQL 8.0