web-dev-qa-db-ja.com

mysqldump --single-transaction、それでも更新クエリはバックアップを待機しています

Mysqldump --single-transactionを使用する場合、ドキュメントによれば、読み取りロックでテーブルをフラッシュして一貫した状態を取得し、トランザクションを開始する必要があります。ライターは待機していません。

しかし、私は昨夜、次の状況に遭遇しました。

show fullプロセスリストからの抜粋:

何百もの...

   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はここでは冗長だと思います、おそらく以前の残り物です、このスクリプトは非常に古いですが、何も傷つけるべきではありません

10

-single-transactionmysqldump のオプションは_FLUSH TABLES WITH READ LOCK;_。これにより、mysqldumpは、ダンプされるすべてのテーブルに対して繰り返し可能な読み取りトランザクションをセットアップします。

あなたの質問から、_db_external_notification_テーブルに対するmysqldumpのSELECTが何百ものINSERTコマンドを同じテーブルに保持していると述べました。なんでこんなことが起こっているの ?

最も可能性が高いのは、 gen_clust_index のロックです(クラスター化インデックスとして知られています)。このパラダイムにより、テーブルのデータページとインデックスページが共存します。これらのインデックスページは、PRIMARY KEYまたは自動生成されたRowIDインデックスに基づいています(PRIMARY KEYがない場合)。

_SHOW ENGINE INNODB STATUS\G_を実行してこれを見つけ、 gen_clust_index から排他ロックのあるページを探すことができます。クラスタ化インデックスのあるテーブルにINSERTを実行するには、PRIMARY KEYのBTREEを処理するための排他ロックと、auto_incrementのシリアル化が必要です。

以前にこの現象について話しました

UPDATE 2014-07-21 15:03 EDT

パストビンの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
_

これは何を教えてくれますか? idにauto_incrementを持ついくつかの主キーがあります。

Mysqldumpの起動時に、テーブル_db_external_notification_の最大idが_1252538391_未満でした。 _1252538391_から_1252538405_を減算すると、14以上のINSERTコマンドが試行されたことを意味します。内部的には、このテーブルのauto_incrementを少なくとも14回移動する必要があります。ただし、このidギャップを管理しているため、ログバッファーにコミットしたりプッシュしたりすることはできません。

次に、ペーストビンのプロセスリストを確認します。誤解しない限り、38のDB接続がINSERTを実行しているのを見ました(19はmysqldumpプロセスの前(プロセスID _6155315_)、19は後)。 auto_incrementギャップを管理しているため、これらの接続の14以上がフリーズしていると思います。

6
RolandoMySQLDBA

mysqldumpdoes--single-transactionオプションは、バックアップジョブを開始する前にFLUSH TABLES WITH READ LOCKを実行しますbut only特定の条件下で。これらの条件の1つは、--master-dataオプションも指定した場合です。

ソースコードの5797行目のmysql-5.6.19/client/mysqldump.cから:

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座標が取得されると解放されます。

実際、mysqldumpFLUSH TABLESを実行し、その後にFLUSH TABLES WITH READ LOCKを実行します。これは、両方の処理を行うと、初期フラッシュに時間がかかる場合に、読み取りロックをより速く取得できるためです。

...しかしながら...

Binlog座標を取得するとすぐに、mysqldumpUNLOCK TABLESステートメントを発行するため、開始したフラッシュの結果として何もブロックされないはずです。また、mysqldumpが保持しているトランザクションの結果として、スレッドがWaiting for table flushになることはありません。

Waiting for table flush状態のスレッドが表示された場合、そのshouldは、FLUSH TABLES [WITH READ LOCK]ステートメントが発行されたことを意味しますそしてまだ実行されていましたクエリの開始時に-クエリは待機する必要があります実行する前にテーブルをフラッシュします。あなたが投稿したプロセスリストの場合、mysqldumpはこの同じテーブルから読み取りを行っており、クエリはしばらく実行されていますが、ブロッキングクエリはその間ずっとブロックされていません。

これはすべて、何か他のことが起こったことを示唆しています。

内部でFLUSH TABLESが機能する方法には、長期にわたる問題があります Bug#44884で説明 。 問題が解決しない場合でも、私は驚かないでしょう、 この問題が解決されると非常に複雑な問題であるため、この問題が「修正」されると驚きます。高並行性環境では本当に修正することは事実上不可能です。修正しようとすると、他の何かを壊す大きなリスクが伴います。または、新しい、異なる、それでも望ましくない動作を作成します。

これはあなたが見ているものの説明になると思われます。

具体的には:

  • テーブルに対して実行されている長時間のクエリがあり、FLUSH TABLESを発行した場合、FLUSH TABLESは長時間のクエリが完了するまでブロックされます。

  • さらに、FLUSH TABLESが発行された後に始まるクエリは、FLUSH TABLESが完了するまでブロックされます。

  • さらに、FLUSH TABLESクエリを強制終了すると、ブロックしているクエリは、元の長時間実行クエリでstill blockになります。これは、FLUSH TABLESクエリをブロックしていたクエリでも、強制終了されたFLUSH TABLESクエリは終了しませんでしたが、そのテーブル(長時間実行クエリに関連する1つ以上)はまだフラッシュされており、保留中のフラッシュは、実行中のクエリは終了しますが、前ではありません。

ここで考えられる結論は、別のプロセス-おそらく別のmysqldump、不適切なクエリ、または不適切に記述された監視プロセスがテーブルをフラッシュしようとしたことです。

その後、そのクエリは不明なメカニズムによって強制終了またはタイムアウトしましたが、その影響は、mysqldumpが問題のテーブルからの読み取りを終了するまで続きました。

長時間実行クエリの処理中にFLUSH TABLESを試行することにより、この状態を再現できます。次に、ブロックする別のクエリを開始します。次に、FLUSH TABLESクエリを強制終了します。これにより、最新のクエリのブロックが解除されません。次に、最初のクエリを強制終了するか、終了させます。最後のクエリが正常に実行されます。


後から考えると、これは無関係です。

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

mysqldump --single-transactionSTART TRANSACTION WITH CONSISTENT SNAPSHOTを発行し、ダンプの進行中に変更されたデータをダンプできないため、これは正常です。それがなければ、--single-transactionは主張されているものとは異なるため、最初に取得したbinlog座標は意味がありません。このトランザクションは明らかにロックを保持していないため、これはWaiting for table flushの問題とは関係ありません。

10

機能リクエストを送信しました: https://support.Oracle.com/epmos/faces/BugDisplay?id=27103902

--single-transaction --master-dataと--single-transaction --slave-dataの組み合わせと同じ方法を使用する5.6.37に対するパッチも作成しました。これは、保証なしで現状のまま提供されます。自己責任。

--- 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. 手順3のダンプの座標までスレーブAを起動します。
  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. スレーブAのステップ8のダンプから座標までスレーブを開始します。
  12. 手順8のダンプをスレーブAにロードします。
  13. スレーブAからreplicate_ignore_dbおよびskip_slave_startオプションを削除します。
  14. スレーブAを再起動します。
  15. 1週間ほど待ちます。
  16. Pt-checksumを使用して、データの整合性を確認します。

Oracleのパッチ提出プロセスはかなり集中的であるため、なぜ私はこの方法を採用しましたか。 PerconaやMariaDBを使って統合することもできます。

2
user44127