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

10

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

   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 qu'attendent ces inserts? 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 précédents, ce script est très ancien, mais ne devrait pas faire de mal

Aleksandar Ivanisevic
la source
la sortie complète de show full processlist et show innodb status (anonymized) est ici: pastebin.com/D7WS3QAE
Aleksandar Ivanisevic
À quoi sert votre ligne de commande complète mysqldump? En particulier, utilisez-vous --flush-logsou --master-data...? Il existe des interactions potentielles entre les options.
Michael - sqlbot
a ajouté la commande mysqldump complète, merci d'avoir regardé
Aleksandar Ivanisevic

Réponses:

6

L' option --single-transaction de mysqldump ne fonctionne 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 la db_external_notificationtable 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 soit sur la PRIMARY KEY, soit sur un index RowID généré automatiquement (dans le cas où il n'y a pas de PRIMARY KEY).

Vous devriez être capable de repérer cela en exécutant SHOW ENGINE INNODB STATUS\Get en recherchant n'importe quelle page de 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 CLÉ PRIMAIRE avec un auto_increment activé id.

Votre maximum idpour la table db_external_notificationétait inférieur à celui 1252538391du lancement de mysqldump. Lorsque vous soustrayez 1252538391de 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 engagé ou même poussé dans le tampon de journal en raison de la gestion de cet idécart.

Maintenant, regardez la liste des processus de votre PasteBin. Sauf erreur de calcul, j'ai vu 38 connexions DB effectuer un INSERT (19 avant le processus mysqldump (identifiant du processus 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.

RolandoMySQLDBA
la source
Je cherchais depuis longtemps et je n'ai pas trouvé de serrures exclusives. j'ai collé le statut innodb complet de l'émission sur pastebin.com/D7WS3QAE , rien n'y ressemble à un verrou exclusif sur quoi que ce soit pour moi
Aleksandar Ivanisevic
Merci pour la clarification. Je me demande pourquoi ils n'utilisent pas une transaction en lecture seule car il est clair que la sauvegarde n'écrira jamais, mais je suppose qu'ils conservent cette fonctionnalité pour leur sauvegarde d'entreprise.
Aleksandar Ivanisevic
10

L' --single-transactionoption de mysqldump fait un FLUSH TABLES WITH READ LOCKavant de démarrer la tâche de sauvegarde, mais uniquement sous certaines conditions. L'une de ces conditions est lorsque vous spécifiez également l' --master-dataoption.

Dans le code source, à partir mysql-5.6.19/client/mysqldump.cde 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' --master-dataoption déclenche l'obtention de ce verrou, puis le relâche une fois les coordonnées binlog obtenues.

En fait, mysqldumpfait un FLUSH TABLESsuivi d'un FLUSH TABLES WITH READ LOCKparce que 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 UNLOCK TABLESinstruction, il ne devrait donc pas y avoir de blocage à la suite du vidage que vous avez commencé. Aucun thread ne doit non Waiting for table flushplus résulter de la transaction en cours mysqldump.

Lorsque vous voyez un thread dans l' Waiting for table flushétat, cela devrait signifier que l' FLUSH TABLES [WITH READ LOCK]instruction a été émise et était toujours en cours d'exécution au démarrage 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, mysqldumplit à partir de ce même tableau, 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 le FLUSH TABLESfonctionnement 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 à forte concurrence - et toute tentative de le réparer comporte un risque important de casser quelque chose d'autre ou de 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 le FLUSH TABLESbloquera jusqu'à ce que la requête de longue durée se termine.

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

  • en outre, si vous supprimez la FLUSH TABLESrequête, les requêtes qui bloquent seront toujours bloquées sur la requête de longue durée d'origine, celle qui bloquait la FLUSH TABLESrequête, car même si la FLUSH TABLESrequête tuée ne s'est pas terminée, cette table (celle ou plus, impliqué dans la requête de longue durée) est toujours en cours 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'à la mysqldumpfin de la lecture du tableau en question.

Vous pouvez répliquer cette condition en essayant FLUSH TABLESpendant qu'une requête de longue durée est en cours. Ensuite, lancez une autre requête, qui se bloquera. Tuez ensuite la FLUSH TABLESrequête, qui ne débloquera pas la dernière requête. Ensuite, supprimez la première requête ou laissez-la se 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 le mysqldump --single-transactionproblème a START TRANSACTION WITH CONSISTENT SNAPSHOT, qui l'empêche de vider les données modifiées pendant le vidage. Sans cela, les coordonnées binlog obtenues au départ n'auraient aucun sens, car la --single-transactionne serait pas ce qu'elle prétend être. Cela ne devrait en aucun cas être lié au Waiting for table flushproblème, car cette transaction ne comporte évidemment aucun verrou.

Michael - sqlbot
la source
Cette réponse est en fait correcte.
Boban P.
2

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 à partir 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.

user44127
la source