スレーブがMTSをオンにしたときにmysqldumpを実行することによって引き起こされるデッドロックのケース

著者:データベース技術の専門家でも8人の奇妙な(Gao)

オリジナル:https://www.jianshu.com/p/39db1bb5041c

1.問題の原因

これは、次のように顧客から提供されたケースです。showprocesslistのスクリーンショットは次のとおりです。

この問題が手動で発生しない限り、FTWRLセッションを強制終了すると、レプリケーションスレッドを続行できます。バージョンコミュニティバージョン5.7.26。

第二に、ブロック図

上記の閉塞を分析すると、次のように絵を描くことができます。

3つ目は、ワーカースレッドw1とw3の待機についてです。

ここでは、パラメータslave_preserve_commit_orderに焦点を当てる必要があります 。これについては、私が発行する「MySQLマスタースレーブの原則の詳細な理解」という本で詳しく説明されています。

  • このパラメーターは、スレーブライブラリのグループコミット内の各ワーカースレッドのトランザクション送信順序が、メインライブラリトランザクションの実行順序と一致するようにするためのものです。注文コミットのフラッシュフェーズの前に有効になります。ワーカースレッドのトランザクションは、コミット許可の取得を待機している間、「先行するトランザクションのコミットを待機中」の状態でブロックされます。

ただし、MDL_key :: COMMITは、注文コミットのフラッシュの前に取得されることがわかっています。したがって、w1およびw3ワーカースレッドは送信許可の到着を待機していますが、グローバル読み取りロックを取得できないため、残念ながらw2のトランザクションを送信できません。同時に、彼らはFTWRLをブロックしました。

第四に、FTWRLを待っています

私はこれを何度も説明しましたが、FTWRLのプロセスはおおよそ次のとおりです。

最初のステップ:  MDL LOCKタイプをGLOBALに追加し、レベルをSに追加します。待機状態が表示された場合は、「グローバル読み取りロックを待機中」です。selectステートメントはGLOBALレベルではロックされませんが、DML / DDL / FOR UPDATEステートメントはGLOBALレベルのIXロックでロックされ、IXロックとSロックの非互換性によりこの種の待機が発生することに注意してください。互換性マトリックスは次のとおりです。

          | Type of active   |
  Request |   scoped lock    |
   type   | IS(*)  IX   S  X |
 ---------+------------------+
 IS       |  +      +   +  + |
 IX       |  +      +   -  - |
 S        |  +      -   +  - |
 X        |  +      -   -  - |

ステップ2: グローバルテーブルのキャッシュバージョンを進めます。ソースコードはグローバル変数refresh_version ++です。 手順3: 未使用のテーブルキャッシュを解放します。関数close_cached_tablesを自分で参照できます。 手順4: 占有されているテーブルキャッシュがあるかどうかを確認し、ある場合は、占有者が解放されるのを待ちます。待機状態は「テーブルフラッシュを待機中」です。この手順では、テーブルキャッシュのバージョンがグローバルテーブルキャッシュのバージョンと一致するかどうかを判断します。一致しない場合は、次のように待ちます。

for (uint idx=0 ; idx < table_def_cache.records ; idx++) 
     {
       share= (TABLE_SHARE*) my_hash_element(&table_def_cache, idx); //寻找整个 table cache shared hash结构
       if (share->has_old_version()) //如果版本 和 当前 的 refresh_version 版本不一致
       {
         found= TRUE;
         break; //跳出第一层查找 是否有老版本 存在
       }
     }
...
if (found)//如果找到老版本,需要等待
   {
     /*
       The method below temporarily unlocks LOCK_open and frees
       share's memory.
     */
     if (share->wait_for_old_version(thd, &abstime,
                                   MDL_wait_for_subgraph::DEADLOCK_WEIGHT_DDL))
     {
       mysql_mutex_unlock(&LOCK_open);
       result= TRUE;
       goto err_with_reopen;
     }
   }

待機の終了は、占有テーブルキャッシュの占有者の解放です。この解放操作は、次のように関数close_thread_tableに存在します。

if (table->s->has_old_version() || table->needs_reopen() ||
      table_def_shutdown_in_progress)
  {
    tc->remove_table(table);//关闭 table cache instance
    mysql_mutex_lock(&LOCK_open);
    intern_close_table(table);//去掉 table cache define
    mysql_mutex_unlock(&LOCK_open);
  }

最終的に、関数MDL_wait :: set_statusが呼び出されてFTWRLがウェイクアップされます。つまり、占有テーブルキャッシュの解放は、FTWRLセッションではなく、占有者自身です。いずれの場合も、最終的にテーブルキャッシュ全体が空になります。FTWRLの後でOpen_table_definitionsとOpen_tablesをチェックすると、カウントが再カウントされていることがわかります。ウェイクアップ機能のコードは次のとおりです。これも明らかです。

bool MDL_wait::set_status(enum_wait_status status_arg) open_table
{
  bool was_occupied= TRUE;
  mysql_mutex_lock(&m_LOCK_wait_status);
  if (m_wait_status == EMPTY)
  {
    was_occupied= FALSE;
    m_wait_status= status_arg;
    mysql_cond_signal(&m_COND_wait_status);//唤醒
  }
  mysql_mutex_unlock(&m_LOCK_wait_status);//解锁
  return was_occupied;
}

手順5:  MDLLOCKタイプのCOMMITレベルをSに追加します。待機状態の場合は「コミットロック待ち」です。この種の待機は、大規模なトランザクションコミットがある場合に発生する可能性があります。

 ここで5番目のステップに注意してください。w1とw3がMDLLOCK COMMITを取得し、w2のトランザクションがコミットするのを待っているため、FTWRLは待機する必要があります。

5、ワーカースレッドw2の待機について

ここには2つの理由が考えられます。

  • マルチスレッド並列処理の場合、スレッドの実行順序は本質的に不確実です。CPUスケジューリングの最小単位はスレッドであるため、CPUの損失により、スレッドが他のスレッドより遅れる可能性があります。共有メモリ操作の整合性を確保するには、ミューテックスやアトミック変数などのテクノロジが必要です。

  • w2のトランザクションに本質的に複数のDMLステートメントが含まれている場合、GLOBAL READ LOCK自体の取得は断続的です。つまり、各ステートメントの最後に解放され、次のステートメントの開始時にテーブルを再度開きます。

2番目のポイントを見てみましょう。row_format形式のbinlogのみを検討してください。

トランザクションには複数のステートメントを含めることができます。各ステートメントには、マップイベントと複数のDMLイベントが含まれます。このイベントがステートメントの最後のイベントである場合、STMT_END_Fでマークされ、この時点でGLOBALがリリースされます。 READ LOCK、ソースコードは次のとおりです。

if (get_flags(STMT_END_F))
  {
    if((error= rows_event_stmt_cleanup(rli, thd)))

栈:
#0  MDL_context::release_lock (this=0x7fffa8000a08, duration=MDL_STATEMENT, ticket=0x7fffa800ea40) at /opt/percona-server-locks-detail-5.7.22/sql/mdl.cc:4350
#1  0x0000000001464bf1 in MDL_context::release_locks_stored_before (this=0x7fffa8000a08, duration=MDL_STATEMENT, sentinel=0x0) at /opt/percona-server-locks-detail-5.7.22/sql/mdl.cc:4521
#2  0x000000000146541b in MDL_context::release_statement_locks (this=0x7fffa8000a08) at /opt/percona-server-locks-detail-5.7.22/sql/mdl.cc:4813
#3  0x0000000001865c75 in Relay_log_info::slave_close_thread_tables (this=0x341e8b0, thd=0x7fffa8000970) at /opt/percona-server-locks-detail-5.7.22/sql/rpl_rli.cc:2014
#4  0x0000000001865873 in Relay_log_info::cleanup_context (this=0x341e8b0, thd=0x7fffa8000970, error=false) at /opt/percona-server-locks-detail-5.7.22/sql/rpl_rli.cc:1886
#5  0x00000000017e8fc7 in rows_event_stmt_cleanup (rli=0x341e8b0, thd=0x7fffa8000970) at /opt/percona-server-locks-detail-5.7.22/sql/log_event.cc:11782
#6  0x00000000017e8c79 in Rows_log_event::do_apply_event (this=0x7fffa8017dc0, rli=0x341e8b0) at /opt/percona-server-locks-detail-5.7.22/sql/log_event.cc:11660
#7  0x00000000017cfdcd in Log_event::apply_event (this=0x7fffa8017dc0, rli=0x341e8b0) at /opt/percona-server-locks-detail-5.7.22/sql/log_event.cc:3570
#8  0x00000000018476dc in apply_event_and_update_pos (ptr_ev=0x7fffec14f880, thd=0x7fffa8000970, rli=0x341e8b0) at /opt/percona-server-locks-detail-5.7.22/sql/rpl_slave.cc:4766
#9  0x0000000001848d9a in exec_relay_log_event (thd=0x7fffa8000970, rli=0x341e8b0) at /opt/percona-server-locks-detail-5.7.22/sql/rpl_slave.cc:5300
#10 0x000000000184f9cc in handle_slave_sql (arg=0x33769a0) at /opt/percona-server-locks-detail-5.7.22/sql/rpl_slave.cc:7543
(gdb) p ticket->m_lock->key.mdl_namespace()
$1 = MDL_key::GLOBAL
(gdb) p ticket->m_type
$2 = MDL_INTENTION_EXCLUSIVE
(gdb) p ticket->m_duration
$3 = MDL_STATEMENT

次のステートメントが再びGLOBALREAD LOCKを取得し始めた場合、これは私が断続的なアクセスと呼んでいるものです。


この時点で、デッドロック状態は成熟しています。この状況が発生している限り、継続するには人間の介入が必要になる場合があります。

6、mysqldumpについて

コミュニティエディションでは、次の状況でFTWRLを追加する必要があります。

  • マスターデータを設定する

  • singal-transactionとflush-logsを設定します

perconaバージョンでは、次の状況でFTWRLを追加する必要があります。

  • singal-transactionとflush-logsを設定します

次のようにコードのコミュニティバージョン(コードバージョン8.0.21)を見てみましょう。以下は、FTWRLからUNLOCKを注ぐプロセスです。

 if ((opt_lock_all_tables || opt_master_data || //如果设置了 master data 设置flush table with read lock
       (opt_single_transaction && flush_logs)) &&//如果设置了single transaction和flush logs 设置flush table with read lock
      do_flush_tables_read_lock(mysql)) //设置flush table with read lock
    goto err;
  /*
  /*
    Flush logs before starting transaction since
    this causes implicit commit starting mysql-5.5.
  */
  if (opt_lock_all_tables || opt_master_data || 
      (opt_single_transaction && flush_logs) || opt_delete_master_logs) {
    if (flush_logs || opt_delete_master_logs) {//如果设置了 flush logs 进行日志刷新
      if (mysql_refresh(mysql, REFRESH_LOG)) { //进行日志刷新
        DB_error(mysql, "when doing refresh");
        goto err;
      }
      verbose_msg("-- main : logs flushed successfully!\n");
    }

    /* Not anymore! That would not be sensible. */
    flush_logs = false;
  }

  if (opt_delete_master_logs) {
    if (get_bin_log_name(mysql, bin_log_name, sizeof(bin_log_name))) goto err;
  }

  if (opt_single_transaction && start_transaction(mysql)) goto err; //开启事务 RR

  /* Add 'STOP SLAVE to beginning of dump */
  if (opt_slave_apply && add_stop_slave()) goto err;

  /* Process opt_set_gtid_purged and add SET @@GLOBAL.GTID_PURGED if required.
   */
  if (process_set_gtid_purged(mysql)) goto err; //设置GTID,如果设置了gtid_purged 这个函数会跳过

  if (opt_master_data && do_show_master_status(mysql)) goto err; //获取主库binlog位置
  if (opt_slave_data && do_show_slave_status(mysql)) goto err; //slave_data 设置相关 从show slave中获取
  if (opt_single_transaction &&
      do_unlock_tables(mysql)) /* unlock but no commit! */
    goto err;

次のように、判定関数check_consistent_binlog_posがperconaバージョンに追加されます(ただし、説明は多すぎません)。

  if (opt_single_transaction && opt_master_data)
  {
    /*
       See if we can avoid FLUSH TABLES WITH READ LOCK with Binlog_snapshot_*
       variables.
    */
    consistent_binlog_pos= check_consistent_binlog_pos(NULL, NULL);
  }

  if ((opt_lock_all_tables || (opt_master_data && !consistent_binlog_pos) ||//consistent_binlog_pos 0 需要 1 不需要
       (opt_single_transaction && flush_logs)))
  {
    if (do_flush_tables_read_lock(mysql))
      goto err;
  }

セブン、解決する方法

次のように要約します。

  • マスターデータは通常、バックアップで増加するため、影響を最小限に抑えるために、バックアップはピークの低い期間にのみ実行できます。

  • パラメータslave_preserve_commit_orderをオフにすることを検討してください。ただし、FTWRLのブロックはまだ存在しますが、デッドロックは発生しません。

  • 圧力が大きくない場合は、MTSを閉じることを検討してください。ただし、FTWRLのブロックはまだ存在しますが、デッドロックは発生しません。

全文は終わりました。

MySQLをお楽しみください:)

コードをスキャンして作成者WeChatを追加します

TeacherYeの「MySQLCoreOptimization」クラスがMySQL8.0にアップグレードされました。コードをスキャンして、MySQL8.0の練習の旅を始めてください。

おすすめ

転載: blog.csdn.net/n88Lpo/article/details/111148053