web-dev-qa-db-fra.com

mysqldump --single-transaction, mais les requêtes de mise à jour attendent la sauvegarde

Si j'utilise mysqldump --single-transaction, selon les documents, il devrait faire des tables de vidage avec verrou en lecture pour obtenir un état cohérent, puis démarrer une transaction et aucun écrivain ne devrait attendre.

Cependant, j'ai constaté hier soir la situation suivante:

extrait de la liste complète des processus:

des centaines de ceux-là ...

   Command: Query
   Time: 291
   State: Waiting for table flush
   Info: insert into db_external_notification.....

ensuite ceci:

Command: Query
Time: 1204
State: Sending data
Info: SELECT /*!40001 SQL_NO_CACHE */ * FROM `db_external_notification`

et le reste des fils sont en veille

quelqu'un a-t-il une idée de ce que ces insertions attendent? Je ne vois aucune table FLUSH ou DDL ou quoi que ce soit mentionné dans le manuel qui puisse faire attendre les requêtes.

commande mysqldump complète

mysqldump --quick --add-drop-table --single-transaction --master-data=2 -uxx -pxx dbname

Je suppose que --quick est redondant ici, probablement un reste des temps anciens, ce script est très ancien, mais ne devrait pas faire de mal

10

L'option - single-transaction de mysqldump ne fait pas FLUSH TABLES WITH READ LOCK;. Il provoque mysqldump pour configurer une transaction de lecture répétable pour toutes les tables en cours de vidage.

De votre question, vous avez déclaré que le SELECT de mysqldump pour le db_external_notification table contient des centaines de commandes INSERT sur cette même table. Pourquoi cela arrive-t-il ?

La chose la plus probable est un verrou sur le gen_clust_index (mieux connu sous le nom d'index clusterisé). Ce paradigme fait coexister des données et des pages d'index pour une table. Ces pages d'index sont basées sur la PRIMARY KEY ou sur l'index RowID généré automatiquement (dans le cas où il n'y a pas de PRIMARY KEY).

Vous devriez pouvoir le détecter en exécutant SHOW ENGINE INNODB STATUS\G et recherchez n'importe quelle page du gen_clust_index qui a un verrou exclusif. Faire des INSERT dans une table avec un index clusterisé nécessite un verrou exclusif pour gérer le BTREE de la PRIMARY KEY, ainsi que la sérialisation de l'auto_increment.

J'ai déjà discuté de ce phénomène

MISE À JOUR 2014-07-21 15:03 EDT

Veuillez regarder les lignes 614-617 de votre PastBin

mysql tables in use 1, locked 0
MySQL thread id 6155315, OS thread handle 0x85f11b70, query id 367774810 localhost root Sending data
SELECT /*!40001 SQL_NO_CACHE */ * FROM `db_external_notification`
Trx read view will not see trx with id >= 1252538405, sees < 1252538391

Notez que la ligne 617 indique

Trx read view will not see trx with id >= 1252538405, sees < 1252538391

Qu'est-ce que cela me dit? Vous avez une PRIMARY KEY avec un auto_increment sur id.

Votre max id pour la table db_external_notification était inférieur à 1252538391 lors du lancement de mysqldump. Lorsque vous soustrayez 1252538391 de 1252538405, cela signifie que 14 commandes INSERT ou plus ont été tentées. En interne, cela devrait déplacer au moins 14 fois l'auto_increment de cette table. Pourtant, rien ne peut être validé ou même poussé dans le tampon de journal en raison de la gestion de cet écart id.

Maintenant, regardez la liste des processus de votre Pastebin. Sauf erreur de compte, j'ai vu 38 connexions DB effectuer un INSERT (19 avant le processus mysqldump (processus id 6155315), 19 Après). Je suis sûr que 14 ou plus de ces connexions sont gelées en raison de la gestion de l'écart auto_increment.

6
RolandoMySQLDBA

L'option --single-transaction De mysqldump fait faire un FLUSH TABLES WITH READ LOCK Avant de démarrer la tâche de sauvegarde mais seulement sous certaines conditions. L'une de ces conditions est lorsque vous spécifiez également l'option --master-data.

Dans le code source, à partir de mysql-5.6.19/client/mysqldump.c À la ligne 5797:

if ((opt_lock_all_tables || opt_master_data ||
     (opt_single_transaction && flush_logs)) &&
    do_flush_tables_read_lock(mysql))
  goto err;

Pour obtenir un verrou solide sur les coordonnées binlog précises avant de commencer la transaction de lecture répétable, l'option --master-data Déclenche l'obtention de ce verrou, puis le relâche une fois les coordonnées binlog obtenues.

En fait, mysqldump fait un FLUSH TABLES Suivi d'un FLUSH TABLES WITH READ LOCK Car faire les deux choses permet d'obtenir le verrou de lecture plus rapidement dans les cas où le vidage initial prend un certain temps.

...toutefois...

Dès qu'il a obtenu les coordonnées binlog, mysqldump émet une instruction UNLOCK TABLES, Donc il ne devrait pas y avoir de blocage à la suite du vidage que vous avez commencé. Aucun thread ne doit non plus être Waiting for table flush À la suite de la transaction que mysqldump détient.

Lorsque vous voyez un thread dans l'état Waiting for table flush, Cela devrait signifie que l'instruction FLUSH TABLES [WITH READ LOCK] A été émise et était toujours en cours d'exécution = au début de la requête - la requête doit donc attendre le vidage de la table avant de pouvoir s'exécuter. Dans le cas de la liste de processus que vous avez publiée, mysqldump lit dans cette même table et la requête est en cours d'exécution depuis un certain temps, mais les requêtes de blocage ne bloquent pas depuis si longtemps.

Tout cela suggère que quelque chose d'autre s'est produit.

Il y a un problème de longue date expliqué dans le bogue # 44884 avec la façon dont FLUSH TABLES Fonctionne, en interne. Je ne serais pas surpris si le problème persiste, Je serais surpris si ce problème est jamais "résolu" car il s'agit d'un problème très complexe à résoudre - pratiquement impossible à résoudre véritablement dans un environnement à concurrence élevée - et toute tentative de le résoudre comporte un risque important de casser quelque chose d'autre, ou créer un comportement nouveau, différent et toujours indésirable.

Il semble probable que ce sera l'explication de ce que vous voyez.

Plus précisément:

  • si vous avez une requête de longue durée exécutée sur une table et émettez FLUSH TABLES, alors FLUSH TABLES se bloquera jusqu'à la fin de la requête de longue durée.

  • en outre, toutes les requêtes qui commencent après l'émission de FLUSH TABLES seront bloquées jusqu'à ce que FLUSH TABLES soit terminé.

  • en outre, si vous supprimez la requête FLUSH TABLES, les requêtes qui bloquent bloqueront toujours sur la requête de longue durée d'origine, celle qui bloquait la requête FLUSH TABLES, car même si la requête FLUSH TABLES supprimée ne s'est pas terminée, cette table (celle ou plus impliquée dans la requête de longue durée) est toujours dans le processus de vidage, et ce vidage en attente va se produire dès que la requête de longue durée se termine - mais pas avant.

La conclusion probable ici est qu'un autre processus - peut-être un autre mysqldump, ou une requête mal avisée, ou un processus de surveillance mal écrit a tenté de vider une table.

Cette requête a ensuite été supprimée ou expirée par un mécanisme inconnu, mais ses séquelles ont persisté jusqu'à ce que mysqldump ait terminé la lecture de la table en question.

Vous pouvez répliquer cette condition en essayant de FLUSH TABLES Pendant qu'une requête de longue durée est en cours. Ensuite, lancez une autre requête, qui se bloquera. Tuez ensuite la requête FLUSH TABLES, Qui ne débloquera pas la dernière requête. Ensuite, supprimez la première requête ou laissez-la terminer, et la requête finale s'exécutera avec succès.


Après coup, cela n'est pas lié:

Trx read view will not see trx with id >= 1252538405, sees < 1252538391

C'est normal, car mysqldump --single-transaction Émet un START TRANSACTION WITH CONSISTENT SNAPSHOT, Ce qui l'empêche de vider les données modifiées pendant le vidage. Sans cela, les coordonnées binlog obtenues au début n'auraient aucun sens, car le --single-transaction Ne serait pas ce qu'il prétend être. Cela ne devrait en aucun cas être lié au problème Waiting for table flush, Car cette transaction ne contient évidemment aucun verrou.

10
Michael - sqlbot

J'ai soumis une demande de fonctionnalité: https://support.Oracle.com/epmos/faces/BugDisplay?id=27103902 .

J'ai également écrit un correctif contre 5.6.37 qui utilise la même méthode que la combinaison --single-transaction --master-data avec --single-transaction --slave-data, qui est fourni tel quel sans garantie. À utiliser à vos risques et périls.

--- mysql-5.6.37/client/mysqldump.c.bak 2017-11-14 12:24:41.846647514 -0600
+++ mysql-5.6.37/client/mysqldump.c 2017-11-14 14:17:51.187050091 -0600
@@ -4900,10 +4900,10 @@
   return 0;
 }

+/*
 static int do_stop_slave_sql(MYSQL *mysql_con)
 {
   MYSQL_RES *slave;
-  /* We need to check if the slave sql is running in the first place */
   if (mysql_query_with_error_report(mysql_con, &slave, "SHOW SLAVE STATUS"))
     return(1);
   else
@@ -4911,23 +4911,21 @@
     MYSQL_ROW row= mysql_fetch_row(slave);
     if (row && row[11])
     {
-      /* if SLAVE SQL is not running, we don't stop it */
       if (!strcmp(row[11],"No"))
       {
         mysql_free_result(slave);
-        /* Silently assume that they don't have the slave running */
         return(0);
       }
     }
   }
   mysql_free_result(slave);

-  /* now, stop slave if running */
   if (mysql_query_with_error_report(mysql_con, 0, "STOP SLAVE SQL_THREAD"))
     return(1);

   return(0);
 }
+*/

 static int add_stop_slave(void)
 {
@@ -5841,10 +5839,12 @@
   if (!path)
     write_header(md_result_file, *argv);

+  /*
   if (opt_slave_data && do_stop_slave_sql(mysql))
     goto err;
+  */

-  if ((opt_lock_all_tables || opt_master_data ||
+  if ((opt_lock_all_tables || opt_master_data || opt_slave_data ||
        (opt_single_transaction && flush_logs)) &&
       do_flush_tables_read_lock(mysql))
     goto err;
@@ -5853,7 +5853,7 @@
     Flush logs before starting transaction since
     this causes implicit commit starting mysql-5.5.
   */
-  if (opt_lock_all_tables || opt_master_data ||
+  if (opt_lock_all_tables || opt_master_data || opt_slave_data ||
       (opt_single_transaction && flush_logs) ||
       opt_delete_master_logs)
   {
 static int add_stop_slave(void)
 {
@@ -5841,10 +5839,12 @@
   if (!path)
     write_header(md_result_file, *argv);

+  /*
   if (opt_slave_data && do_stop_slave_sql(mysql))
     goto err;
+  */

-  if ((opt_lock_all_tables || opt_master_data ||
+  if ((opt_lock_all_tables || opt_master_data || opt_slave_data ||
        (opt_single_transaction && flush_logs)) &&
       do_flush_tables_read_lock(mysql))
     goto err;
@@ -5853,7 +5853,7 @@
     Flush logs before starting transaction since
     this causes implicit commit starting mysql-5.5.
   */
-  if (opt_lock_all_tables || opt_master_data ||
+  if (opt_lock_all_tables || opt_master_data || opt_slave_data ||
       (opt_single_transaction && flush_logs) ||
       opt_delete_master_logs)
   {

Je l'ai testé avec le processus suivant avec des esclaves vers un maître très occupé en utilisant beaucoup de tables InnoDB avec des relations FK:

  1. Arrêtez l'esclave A.
  2. Attendez ~ 15 minutes.
  3. Dump DB 1 de l'esclave B avec l'option --single-transaction et --dump-slave = 2
  4. Démarrez l'esclave A jusqu'à ce que les coordonnées dans le vidage de l'étape 3.
  5. Supprimez DB 1 et 2 de l'esclave A.
  6. Créez des DB 1 et 2 vides sur l'esclave A.
  7. Charger le vidage de l'étape 3 dans l'esclave A.
  8. Vider DB 2 de l'esclave B avec les mêmes options. DB 2 a des relations FK avec DB 1.
  9. Ajoutez replicate_ignore_db pour DB 2 et skip_slave_start sur l'esclave A.
  10. Redémarrez l'esclave A.
  11. Démarrer l'esclave jusqu'à ce que les coordonnées du vidage à l'étape 8 sur l'esclave A.
  12. Charger le vidage de l'étape 8 dans l'esclave A.
  13. Supprimez les options replicate_ignore_db et skip_slave_start de l'esclave A.
  14. Redémarrez l'esclave A.
  15. Attendez ~ 1 semaine.
  16. Utilisez pt-checksum pour vérifier l'intégrité des données.

Le processus de soumission des correctifs d'Oracle est assez intensif, c'est pourquoi j'ai choisi cette voie. Je peux essayer avec Percona et/ou MariaDB pour l'intégrer.

2
user44127