Un scénario d'application de lecture semi-cohérente MySQL causé par un problème de journal lent

A travers un problème de log lent, l'auteur présente le concept et les scénarios d'application pratique de la lecture semi-cohérente de MySQL.

Auteur : Gong Tangjie

Membre de l'équipe Acson DBA, principalement responsable du support technique MySQL, doué pour MySQL, PG et les bases de données nationales.

Source de cet article : contribution originale

  • Produit par la communauté open source Aikesheng, le contenu original ne peut être utilisé sans autorisation, veuillez contacter l'éditeur et indiquer la source pour la réimpression.

arrière-plan

Un certain système a constaté que l'opération de mise à jour était très lente et qu'il y avait un grand nombre de journaux lents, parmi lesquels le temps de verrouillage représentait une proportion élevée. La version MySQL était 5.7.25 et le niveau d'isolement était RR.

analyser

Affichez la structure du tableau et UPDATEle plan d'exécution de l'instruction :

mysql> show create table test;

+-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| test | CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(30) COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2621401 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin |
+-------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> explain update test set name ='test' where name='a';
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
| 1  | UPDATE      | test  | NULL       | index | NULL   | PRIMARY | 4 | NULL | 2355988 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
1 row in set (0.00 sec)

Grâce au plan d'exécution, on constate que le SQL est une analyse complète de l'index de la clé primaire et que la namecolonne n'est pas indexée. Lorsque plusieurs transactions sont exécutées en même temps, un blocage sera observé.

opération 1 opération 2
mysql> commencer ;
Requête OK, 0 lignes affectées (0.00 sec)
mysql> update test set name='test' where name='a';
Requête OK, 262144 lignes affectées (4,67 s)
Lignes correspondantes : 262144 modifiées : 262144 avertissements : 0
mysql> commencer ;
Requête OK, 0 lignes affectées (0.00 sec)
mysql> update test set name ='test1' where name='b';

S'il namen'y a pas beaucoup de valeurs en double dans la colonne, vous pouvez nameajouter un index à la colonne pour résoudre le problème. Étant donné que le mécanisme de verrouillage de ligne d'InnoDB est implémenté sur la base de la colonne d'index, si l' UPDATEinstruction peut utiliser namel'index de la colonne, il n'y aura pas de blocage, ce qui entraînera un gel des activités.

Cependant, si namela discrimination des valeurs de colonne est très faible, SQL n'exécutera pas namel'index de colonne, comme le montre l'exemple suivant :

ajouter d'abord l'index

mysql> alter table test add index tt(name);
Query OK, 0 rows affected (2.74 sec)
Records: 0 Duplicates: 0 Warnings: 0

Ensuite, vérifiez le plan d'exécution et constatez qu'il existe des index qui peuvent être utilisés tt, mais la situation réelle est toujours l'analyse complète de l'index de la clé primaire.

mysql> explain update test set name ='test' where name='a';
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
| 1 | UPDATE | test | NULL | index | tt | PRIMARY | 4 | NULL | 2355988 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+---------+----------+-------------+
1 row in set (0.00 sec)

Étant donné que l'optimiseur de MySQL est évalué en fonction du coût, nous pouvons optimizer tracel'observer par .

mysql> show variables like 'optimizer_trace';
+-----------------+--------------------------+
| Variable_name | Value |
+-----------------+--------------------------+
| optimizer_trace | enabled=off,one_line=off |
+-----------------+--------------------------+
1 row in set (0.01 sec)

Vous pouvez voir la valeur enabled=off, indiquant que cette fonction est désactivée par défaut.

Si vous souhaitez activer cette fonction, vous devez d'abord enabledchanger la valeur de en on.

mysql> set optimizer_trace="enabled=on";
Query OK, 0 rows affected (0.00 sec)

Exécutez ensuite le SQL pour afficher les informations détaillées. Ici, nous nous concentrons principalement sur le calcul des coûts dans l'étape PREPARE .

mysql> update test set name ='test' where name='a';
Query OK, 262144 rows affected (5.97 sec)
Rows matched: 262144 Changed: 262144 Warnings: 0

mysql> SELECT * FROM information_schema.OPTIMIZER_TRACE\G 

Les résultats détaillés sont les suivants.

mysql> SELECT * FROM information_schema.OPTIMIZER_TRACE\G
*************************** 1. row ***************************
QUERY: update test set name ='test' where name='a'
TRACE: {
"steps": [
{
"substitute_generated_columns": {
}
},
{
"condition_processing": {
"condition": "WHERE",
"original_condition": "(`test`.`name` = 'a')",
"steps": [
{
"transformation": "equality_propagation",
"resulting_condition": "multiple equal('a', `test`.`name`)"
},
{
"transformation": "constant_propagation",
"resulting_condition": "multiple equal('a', `test`.`name`)"
},
{
"transformation": "trivial_condition_removal",
"resulting_condition": "multiple equal('a', `test`.`name`)"
}
]
}
},
{
"table": "`test`",
"range_analysis": {
"table_scan": {
"rows": 2355988,
"cost": 475206
},
"potential_range_indexes": [
{
"index": "PRIMARY",
"usable": true,
"key_parts": [
"id"
]
},
{
"index": "tt",
"usable": true,
"key_parts": [
"name",
"id"
]
}
],
"setup_range_conditions": [
],
"group_index_range": {
"chosen": false,
"cause": "no_join"
},
"analyzing_range_alternatives": {
"range_scan_alternatives": [
{
"index": "tt",
"ranges": [
"0x0100610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 <= name <= 0x0100610000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
],
"index_dives_for_eq_ranges": true,
"rowid_ordered": true,
"using_mrr": false,
"index_only": false,
"rows": 553720,
"cost": 664465,
"chosen": false,
"cause": "cost"
}
],
"analyzing_roworder_intersect": {
"usable": false,
"cause": "too_few_roworder_scans"
}
}
}
}
]
}
MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 0
INSUFFICIENT_PRIVILEGES: 0
1 row in set (0.00 sec)

On peut constater que le coût d'exécution d'une analyse complète de la table est de 475206 et que le coût d'exécution d'un index ttest de 664465 , donc MySQL choisit une analyse complète de la table .

Alors si tel est le cas, comment y faire face ?

Si le niveau d'isolation InnoDB est RR, il n'y a pas de bon moyen au niveau de la base de données, et il est recommandé de modifier le côté application.

Si le niveau d'isolation de la base de données peut être modifié, il peut être modifié en RC pour résoudre le problème de blocage. Parce que la lecture semi-cohérente est prise en charge en mode RC.

Qu'est-ce qu'une lecture semi-cohérente ?

Pour le dire simplement, lorsqu'une ligne doit être verrouillée, il faudra une étape de plus pour juger si la ligne doit vraiment être verrouillée. Par exemple, lorsque la table complète est analysée et mise à jour, nous n'avons qu'à mettre à jour les WHERElignes correspondantes. S'il n'y a pas de lecture semi-cohérente, toutes les données seront verrouillées, mais avec une lecture semi-cohérente, alors il sera jugé si le WHEREcondition est remplie. Si non Si elle est satisfaite, le verrou ne sera pas ajouté (le verrou sera libéré à l'avance).

Ensuite, pour les champs à faible discrimination, vous pouvez utiliser la fonctionnalité de lecture semi-cohérente pour optimiser, afin que la mise à jour de différentes valeurs ne s'attende pas, provoquant des gels d'activité.

opération 1 opération 2
mysql> commencer ;
Requête OK, 0 lignes affectées (0.00 sec)
mysql> update test set name='test' where name='a';
Requête OK, 262144 lignes affectées (9.30 sec)
Lignes correspondantes : 262144 Modifié : 262144 Avertissements : 0
mysql> commencer ;
Requête OK, 0 lignes affectées (0.00 sec)
mysql> update test set name ='test1' where name='b';
Requête OK, 262 144 lignes affectées (8,46 secondes)
Lignes correspondantes : 262 144 modifiées : 262 144 avertissements : 0

en conclusion

  1. Le mécanisme de verrouillage de ligne est implémenté sur la base des colonnes d'index. Si aucun index n'est utilisé, un parcours complet de la table sera effectué.
  2. La lecture semi-cohérente est une optimisation basée sur le niveau d'isolement RC, qui peut réduire les conflits de verrouillage et l'attente de verrouillage, et améliorer la simultanéité.

À propos de SQLE

Le SQLE de la communauté open source Akson est un outil d'audit SQL pour les utilisateurs et les gestionnaires de bases de données, qui prend en charge l'audit multi-scénarios, prend en charge les processus en ligne standardisés, prend en charge de manière native l'audit MySQL et dispose de types de bases de données évolutives.

SQLE obtenir

taper adresse
Dépôt https://github.com/actiontech/sqle
document https://actiontech.github.io/sqle-docs/
publier des nouvelles https://github.com/actiontech/sqle/releases
Documentation sur le développement du plug-in d'audit de données https://actiontech.github.io/sqle-docs-cn/3.modules/3.7_auditplugin/auditplugin_development.html

Je suppose que tu aimes

Origine blog.csdn.net/ActionTech/article/details/131440819
conseillé
Classement