mysqldump --single-transaction,但是更新查询正在等待备份


10

如果我使用mysqldump --single-transaction,根据文档,它应该使用读锁刷新表以获取一致的状态,然后启动事务,并且没有编写者在等待。

但是,昨晚我遇到了以下情况:

摘录自show full processlist:

数百个...

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

然后这个:

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

其余线程处于睡眠状态

有谁知道这些插入物还在等什么?我没有看到任何FLUSH表或DDL或手册中提到的任何可能导致查询等待的内容。

完整的mysqldump命令

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

我想--quick在这里是多余的,可能是以前的遗留物,这个脚本很旧,但也不会给您带来任何伤害


显示完整进程列表和显示innodb状态(匿名)的完整输出在这里:pastebin.com/D7WS3QAE
Aleksandar Ivanisevic 2014年

您的完整命令行是做mysqldump什么用的?特别是,您在使用--flush-logs还是--master-data...?选项之间存在潜在的相互作用。
Michael-sqlbot

添加了完整的mysqldump命令,感谢您的关注
Aleksandar Ivanisevic

Answers:


6

mysqldump--single-transaction选项不起作用。它使mysqldump为正在转储的所有表设置可重复的读取事务。FLUSH TABLES WITH READ LOCK;

根据您的问题,您说过mysqldump对该db_external_notification表的SELECT 阻止了对该表的数百个INSERT命令。为什么会这样呢?

最有可能的事情是锁定了gen_clust_index(更好的称为聚簇索引)。此范例导致表的数据页和索引页共存。这些索引页基于PRIMARY KEY或自动生成的RowID索引(如果没有PRIMARY KEY)。

您应该能够通过运行发现SHOW ENGINE INNODB STATUS\G并从gen_clust_index中查找具有排他锁的任何页面。在具有聚簇索引的表中执行INSERT操作需要排他锁来处理PRIMARY KEY的BTREE以及auto_increment的序列化。

我以前讨论过这种现象

更新2014-07-21 15:03 EDT

请查看PastBin的614-617行

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

请注意,第617行说

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

这告诉我什么?您有一些PRIMARY KEY,其上具有auto_increment id

id该表的最大值db_external_notification小于1252538391启动mysqldump时的最大值。1252538391从中减去时1252538405,这意味着已尝试14个或更多INSERT命令。在内部,这将需要将此表的auto_increment移动至少14次。但是,由于管理了这一id差距,无法提交任何内容甚至将其推入日志缓冲区。

现在,从您的PasteBin中查看进程列表。除非我算错了,否则我看到38个数据库连接正在执行INSERT(19在mysqldump进程之前(进程id 6155315),19在之后)。我确信由于管理了auto_increment间隙,这些连接中的14个或更多被冻结了。


我找了很长时间,找不到任何排他锁。我已将完整的显示innodb状态粘贴到pastebin.com/D7WS3QAE上,没有什么东西像是对我有独占锁
Aleksandar Ivanisevic 2014年

感谢您的澄清。我想知道为什么他们不使用只读事务,因为很明显备份永远不会写入,但是我猜测他们在为企业备份保留了该功能。
Aleksandar Ivanisevic 2014年

10

--single-transaction的选项mysqldump 一做FLUSH TABLES WITH READ LOCK启动备份作业之前,但只在一定条件下。这些条件之一就是您还指定了--master-data选项。

在源代码中,从mysql-5.6.19/client/mysqldump.c5797行开始:

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

为了在开始可重复读取事务之前获得精确的binlog坐标的固定锁定,该--master-data选项触发此锁定,以获取该锁定,然后在获得binlog坐标后释放该锁定。

实际上,请先mysqldump执行a,FLUSH TABLES然后再执行a ,这是FLUSH TABLES WITH READ LOCK因为在初始刷新需要一些时间的情况下,两者都可以使读取锁定更快地获得。

...然而...

一旦它获得了binlog坐标,就mysqldump发出一条UNLOCK TABLES语句,这样就不会由于开始刷新而造成任何阻塞。任何线程都不应该Waiting for table flush是由于mysqldump保持事务而导致的。

当您看到处于该Waiting for table flush状态的线程时,这应该意味着该FLUSH TABLES [WITH READ LOCK]语句已发出,并且在查询启动时仍在运行 -因此,查询必须等待表刷新后才能执行。对于您已发布的流程列表,它mysqldump是从同一张表中读取的,并且查询已经运行了一段时间,但是阻塞查询并没有这么长时间被阻塞。

这一切都表明发生了其他事情。

在Bug#44884中,存在一个长期存在的问题FLUSH TABLES,即内部的工作方式。 如果问题仍然存在,我不会感到惊讶,如果这个问题曾经得到“解决” 我会感到惊讶,因为这是一个非常复杂的问题需要解决-实际上在高并发环境中无法真正解决-以及任何尝试修复它可能会带来破坏其他事物或创建新的,不同的但仍不理想的行为的巨大风险。

看来这可能就是您所看到的解释。

特别:

  • 如果您有一个针对表运行的长时间运行的查询FLUSH TABLES,然后发出,则FLUSH TABLESwill会阻塞,直到长时间运行的查询完成。

  • 此外,在FLUSH TABLES发出之后开始的所有查询都将阻塞,直到FLUSH TABLES完成为止。

  • 此外,如果您取消了FLUSH TABLES查询,被阻塞的查询仍将在原始的长期运行的查询(被阻塞的FLUSH TABLES查询)上阻塞,因为即使被终止的查询FLUSH TABLES没有完成,该表(那个或涉及长期运行查询的更多内容)仍在刷新过程中,并且只要长期运行查询完成,挂起的刷新将立即发生-但不是在此之前。

这里可能得出的结论是,另一个进程-可能是另一个mysqldump,一个不明智的查询或一个编写不当的监视进程试图刷新表。

该查询随后被未知机制杀死或超时,但是其后遗症一直持续到mysqldump从相关表中读取完为止。

您可以通过尝试FLUSH TABLES运行长时间运行的查询来复制此条件。然后开始另一个查询,这将阻止。然后终止FLUSH TABLES查询,这不会取消阻止最新查询。然后终止第一个查询,或者让它完成,最后一个查询将成功运行。


事后考虑,这无关紧要:

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

这是正常现象,因为mysqldump --single-transaction发出START TRANSACTION WITH CONSISTENT SNAPSHOT,会阻止它转储正在进行转储时更改的数据。--single-transaction否则,开始时获得的二进制日志坐标将毫无意义,因为这将不是它声称的那样。这在任何意义上都不应与此Waiting for table flush问题相关,因为此事务显然没有任何锁。


这个答案实际上是正确的。
Boban P.

2

我提交了功能请求:https : //support.oracle.com/epmos/faces/BugDisplay?id=27103902

我还针对5.6.37编写了一个修补程序,该修补程序使用与--single-transaction --master-data组合和--single-transaction --slave-data相同的方法,该方法按原样提供,不提供任何担保。使用风险自负。

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

我使用许多具有FK关系的InnoDB表,通过以下过程对从属服务器到非常繁忙的主服务器进行了测试:

  1. 停止从站A。
  2. 等待约15分钟。
  3. 使用选项--single-transaction和--dump-slave = 2从从站B转储DB 1
  4. 启动从站A,直到从步骤3开始转储中的坐标为止。
  5. 从从站A删除DB 1和2。
  6. 在从站A上创建空的DB 1和2。
  7. 将步骤3中的转储加载到从站A中。
  8. 使用相同的选项从从站B转储DB 2。DB 2与DB 1具有FK关系。
  9. 为DB 2添加replicate_ignore_db,并在从属A上添加skip_slave_start。
  10. 重新启动从站A。
  11. 启动从站,直到在步骤8中从站A上的转储坐标开始。
  12. 将步骤8中的转储加载到从站A中。
  13. 从从站A中删除copy_ignore_db和skip_slave_start选项。
  14. 重新启动从站A。
  15. 等待〜1周。
  16. 使用pt-checksum验证数据完整性。

Oracle的补丁提交过程相当密集,因此我选择了这条路线。我可以尝试使用Percona和/或MariaDB使其集成。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.