mysqldump - única transação, mas as consultas de atualização estão aguardando o backup

10

Se eu usar o mysqldump --single-transaction, de acordo com os documentos, ele deve liberar tabelas com bloqueio de leitura para obter um estado consistente e iniciar uma transação e nenhum gravador deve estar esperando.

No entanto, eu peguei a seguinte situação ontem à noite:

trecho de show full processlist:

centenas desses ...

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

então isso:

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

e o restante dos threads estão no modo de suspensão

alguém tem alguma idéia do que essas inserções estão esperando? Não vejo nenhuma tabela FLUSH ou DDL ou qualquer coisa mencionada no manual que possa causar a espera das consultas.

comando completo do mysqldump

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

Eu acho que - rápido é redundante aqui, provavelmente sobra de épocas anteriores, esse script é muito antigo, mas não deve prejudicar nada

Aleksandar Ivanisevic
fonte
resultado completo do show de processlist completa e status show de InnoDB (anónimos) está aqui: pastebin.com/D7WS3QAE
Aleksandar Ivanisevic
Qual é a sua linha de comando completa mysqldump? Em particular, você está usando --flush-logsou --master-data...? Existem possíveis interações entre as opções.
Michael - sqlbot
adicionado comando completo do mysqldump, obrigado pela procura
Aleksandar Ivanisevic

Respostas:

6

A opção --single-transaction do mysqldump não funciona FLUSH TABLES WITH READ LOCK;. Isso faz com que o mysqldump configure uma transação de leitura repetível para todas as tabelas que estão sendo despejadas.

Na sua pergunta, você declarou que o SELECT do mysqldump para a db_external_notificationtabela está mantendo centenas de comandos INSERT na mesma tabela. Por que isso está acontecendo ?

A coisa mais provável é um bloqueio no gen_clust_index (mais conhecido como Índice Clusterizado). Esse paradigma faz com que dados e páginas de índice coexistam. Essas páginas de índice são baseadas no índice PRIMARY KEY ou no RowID gerado automaticamente (no caso de não haver PRIMARY KEY).

Você poderá identificar isso executando SHOW ENGINE INNODB STATUS\Ge procurar por qualquer página do gen_clust_index que possua um bloqueio exclusivo. A inserção de INSERTs em uma tabela com um Índice de Cluster exige um bloqueio exclusivo para manipular o BTREE da PRIMARY KEY, bem como a serialização do incremento automático.

Eu já discuti esse fenômeno antes

UPDATE 2014-07-21 15:03 EDT

Veja as linhas 614-617 do seu 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

Observe que a linha 617 diz

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

O que isso me diz? Você tem uma PRIMARY KEY com um auto_increment ativado id.

Seu máximo idpara a tabela db_external_notificationera menor do que 1252538391quando o mysqldump foi lançado. Quando você subtrai 1252538391de 1252538405, isso significa que 14 ou mais comandos INSERT foram tentados. Internamente, isso precisaria mover o incremento automático desta tabela pelo menos 14 vezes. No entanto, nada pode ser confirmado ou empurrado para o Log Buffer por causa do gerenciamento dessa idlacuna.

Agora, veja a lista de processos do seu PasteBin. A menos que eu tenha errado, vi 38 Conexões de Banco de Dados executando um INSERT (19 Antes do processo do mysqldump (identificação do processo 6155315), 19 Depois). Tenho certeza de que 14 ou mais dessas conexões estão congeladas por causa do gerenciamento da lacuna auto_increment.

RolandoMySQLDBA
fonte
Eu estava procurando há muito tempo e não consegui encontrar nenhuma fechadura exclusiva. i ter colado o status show completo innodb no pastebin.com/D7WS3QAE , não há nada parece bloqueio exclusivo em nada para mim
Aleksandar Ivanisevic
Obrigado pelo esclarecimento. Estou me perguntando por que eles não usam uma transação somente leitura, pois é claro que o backup nunca será gravado, mas acho que eles estão mantendo esse recurso para o backup corporativo.
Aleksandar Ivanisevic
10

A --single-transactionopção de mysqldump faz um FLUSH TABLES WITH READ LOCKantes de iniciar a tarefa de backup, mas apenas sob certas condições. Uma dessas condições é quando você também especifica a --master-dataopção.

No código-fonte, mysql-5.6.19/client/mysqldump.cna linha 5797:

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

Para obter um bloqueio sólido nas coordenadas precisas do binlog antes de iniciar a transação de leitura repetível, a --master-dataopção aciona esse bloqueio para ser obtido e liberado depois que as coordenadas do log de bin são obtidas.

De fato, mysqldumpfaz um FLUSH TABLESseguido de um FLUSH TABLES WITH READ LOCKporque fazer as duas coisas permite que o bloqueio de leitura seja obtido mais rapidamente nos casos em que a liberação inicial leva algum tempo.

...Contudo...

Assim que ele obtém as coordenadas do binlog, mysqldumpemite uma UNLOCK TABLESinstrução, portanto não deve haver nada bloqueando como resultado do flush iniciado. Nenhum encadeamento deve ser o Waiting for table flushresultado da transação que mysqldumpestá retendo.

Quando você vê um segmento no Waiting for table flushestado, que deve significar que a FLUSH TABLES [WITH READ LOCK]declaração foi emitida e ainda estava correndo quando a consulta começou - para que a consulta tem que esperar para o flush mesa, antes que ele possa executar. No caso da lista de processos que você postou, mysqldumpestá lendo essa mesma tabela e a consulta está em execução há um tempo, mas as consultas de bloqueio não estão bloqueando há tanto tempo.

Tudo isso sugere que algo mais aconteceu.

Há um problema de longa data explicado no Bug # 44884 com a maneira como FLUSH TABLESfunciona internamente. Eu não ficaria surpreso se o problema ainda persistir, ficaria surpreso se esse problema for "corrigido" porque é um problema muito complexo de resolver - praticamente impossível de ser corrigido em um ambiente de alta simultaneidade - e qualquer tentativa de corrigi-lo acarreta um risco significativo de quebrar outra coisa ou criar um comportamento novo, diferente e ainda indesejável.

Parece provável que esta seja a explicação para o que você está vendo.

Especificamente:

  • se você tiver uma consulta de longa execução em uma tabela e emitir um FLUSH TABLES, o FLUSH TABLESbloco será bloqueado até que a consulta de longa duração seja concluída.

  • Além disso, todas as consultas iniciadas após a FLUSH TABLESemissão serão bloqueadas até a FLUSH TABLESconclusão.

  • Além disso, se você matar a FLUSH TABLESconsulta, as consultas que estão bloqueando ainda serão bloqueadas na consulta de longa execução original, aquela que estava bloqueando a FLUSH TABLESconsulta, porque, embora a FLUSH TABLESconsulta finalizada não tenha terminado, essa tabela (a única ou mais, envolvido com a consulta de execução longa) ainda está em processo de liberação, e essa liberação pendente acontecerá assim que a consulta de execução longa terminar - mas não antes.

A provável conclusão aqui é que outro processo - talvez outro mysqldump, ou uma consulta não recomendada, ou um processo de monitoramento mal escrito tentou liberar uma tabela.

Essa consulta foi posteriormente eliminada ou expirada por um mecanismo desconhecido, mas seus efeitos posteriores permaneceram até mysqldumpterminar a leitura da tabela em questão.

Você pode replicar essa condição tentando FLUSH TABLESenquanto uma consulta de longa execução está em processo. Em seguida, inicie outra consulta, que bloqueará. Em seguida, mate a FLUSH TABLESconsulta, que não desbloqueará a consulta mais recente. Em seguida, elimine a primeira consulta ou deixe-a terminar, e a consulta final será executada com êxito.


Como uma reflexão tardia, isso não tem relação:

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

Isso é normal, porque mysqldump --single-transactionemite a START TRANSACTION WITH CONSISTENT SNAPSHOT, o que impede que ele despeje dados que foram alterados enquanto o despejo estava em andamento. Sem isso, as coordenadas de binlog obtidas no início não teriam sentido, pois --single-transactionnão seria o que afirma ser. Em nenhum sentido, isso não deve estar relacionado ao Waiting for table flushproblema, pois essa transação obviamente não possui bloqueios.

Michael - sqlbot
fonte
Esta resposta está realmente correta.
Boban P.
2

Enviei uma solicitação de recurso: https://support.oracle.com/epmos/faces/BugDisplay?id=27103902 .

Também escrevi um patch contra a 5.6.37 que usa o mesmo método que - única transação - combinação de dados mestre com - única transação - escravo de dados, que é fornecido como está sem garantia. Use por sua conta e risco.

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

Testei-o com o seguinte processo com escravos para um mestre muito ocupado, usando muitas tabelas do InnoDB com relacionamentos FK:

  1. Pare o escravo A.
  2. Aguarde ~ 15 minutos.
  3. Despejar o DB 1 do escravo B com a opção --single-transaction e --dump-slave = 2
  4. Inicie o escravo A até as coordenadas no despejo da etapa 3.
  5. Solte o DB 1 e 2 do escravo A.
  6. Crie o banco de dados 1 e 2 vazio no escravo A.
  7. Carregue o despejo da etapa 3 no escravo A.
  8. Despejar o DB 2 do escravo B com as mesmas opções. O banco de dados 2 possui relacionamentos FK com o banco de dados 1.
  9. Adicione replicate_ignore_db para o DB 2 e skip_slave_start no escravo A.
  10. Reinicie o escravo A.
  11. Inicie o escravo até que as coordenadas sejam despejadas na etapa 8 no escravo A.
  12. Carregue o despejo da etapa 8 no escravo A.
  13. Remova as opções replicate_ignore_db e skip_slave_start do escravo A.
  14. Reinicie o escravo A.
  15. Aguarde ~ 1 semana.
  16. Use pt-checksum para verificar a integridade dos dados.

O processo de envio de patches da Oracle é bastante intenso, portanto, por que eu segui esse caminho. Eu posso tentar com Percona e / ou MariaDB para integrá-lo.

user44127
fonte