Caso de interbloqueo causado por la ejecución de mysqldump cuando el esclavo enciende MTS

Autor: ocho extraños (Gao) también en expertos en tecnología de bases de datos

Original: https://www.jianshu.com/p/39db1bb5041c

1. La fuente del problema

Este es un caso proporcionado por un cliente de la siguiente manera, la captura de pantalla de mostrar lista de procesos es la siguiente:

A menos que este problema se produzca manualmente, finalice la sesión FTWRL y el subproceso de replicación puede continuar. Versión de la comunidad versión 5.7.26.

Segundo, diagrama de bloques

Si analiza el bloqueo anterior, puede dibujar la imagen de la siguiente manera:

Tres, sobre la espera de los hilos de trabajo w1 y w3

Aquí debemos centrarnos en el parámetro  slave_preserve_commit_order , que se describe en detalle en el libro "Comprensión en profundidad de los principios maestro-esclavo de MySQL" que publicaré. Aquí hay una breve descripción de la siguiente manera:

  • Este parámetro es para garantizar que el orden de envío de transacciones de cada subproceso de trabajo en la confirmación de grupo de la biblioteca esclava sea coherente con el orden de ejecución de la transacción de la biblioteca principal. Entra en vigor antes de la fase de descarga de la confirmación de la orden. La transacción del hilo de trabajo se bloqueará en el estado 'Esperando que se confirme la transacción anterior' mientras se espera obtener su permiso de confirmación.

Pero sabemos que MDL_key :: COMMIT se obtendrá antes del vaciado del compromiso de la orden. Por lo tanto, los subprocesos de trabajo w1 y w3 están esperando la llegada de su permiso de envío, pero desafortunadamente la transacción de w2 no se puede enviar porque no puede obtener el bloqueo de lectura global. Al mismo tiempo, bloquearon FTWRL.

Cuarto, la espera en FTWRL

He descrito esto muchas veces, y el proceso de FTWRL es aproximadamente el siguiente:

El primer paso:  agregue el tipo MDL LOCK a GLOBAL, nivel a S. Si aparece el estado de espera, es "Esperando bloqueo de lectura global". Tenga en cuenta que la instrucción select no se bloqueará en el nivel GLOBAL, pero la instrucción DML / DDL / FOR UPDATE se bloqueará en el bloqueo GLOBAL de nivel IX, y la incompatibilidad del bloqueo IX y el bloqueo S provocará este tipo de espera. Aquí está la matriz de compatibilidad:

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

Paso 2:  avance la versión de caché de la tabla global. El código fuente es una variable global refresh_version ++. Paso 3:  libere la caché de la tabla no utilizada. Puede consultar la función close_cached_tables usted mismo. Paso 4:  Determine si hay una memoria caché de mesa ocupada y, si la hay, espere a que se libere el ocupante. El estado de espera es "Esperando a que se descargue la mesa". Este paso determinará si la versión de la caché de la tabla coincide con la versión de la caché de la tabla global. Si no coincide, espere lo siguiente:

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;
     }
   }

El final de la espera es la liberación del ocupante de la caché de tabla ocupada. Esta operación de liberación existe en la función close_thread_table, de la siguiente manera:

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);
  }

Finalmente, se llamará a la función MDL_wait :: set_status para despertar FTWRL, lo que significa que la liberación de la caché de tabla ocupada no es la sesión FTWRL sino el propio ocupante. En cualquier caso, la caché de la tabla completa se vaciará eventualmente. Si marca Open_table_definitions y Open_tables después de FTWRL, encontrará que el recuento se ha vuelto a contar. Aquí está el código para la función de activación, que también es obvio:

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;
}

Paso 5:  Agregue el nivel COMMIT de tipo MDL LOCK a S. Si hay un estado de espera, es 'Esperando bloqueo de confirmación'. Es probable que este tipo de espera se produzca si hay una confirmación de transacción grande.

Preste atención al  quinto paso aquí, es precisamente porque w1 y w3 han adquirido MDL LOCK COMMIT y están esperando que la transacción de w2 se confirme, por lo que FTWRL tiene que esperar.

Cinco, sobre la espera del hilo trabajador w2

Hay 2 posibles razones aquí:

  • En el caso del paralelismo de subprocesos múltiples, el orden de ejecución de los subprocesos es intrínsecamente incierto. Es posible que los subprocesos se retrasen con respecto a otros subprocesos debido a la pérdida de CPU, porque la unidad más pequeña de programación de CPU es el subproceso. Para garantizar la integridad de una operación de memoria compartida, se necesitan tecnologías como mutex y variables atómicas.

  • Si la transacción en w2 contiene inherentemente múltiples declaraciones DML, entonces la obtención del BLOQUEO DE LECTURA GLOBAL en sí es intermitente, es decir, se liberará al final de cada declaración y luego se abrirá la tabla nuevamente cuando comience la siguiente declaración.

Echemos un vistazo al segundo punto, solo consideremos el binlog en formato row_format.

Sabemos que una transacción puede contener múltiples declaraciones. Cada declaración contiene un Evento de mapa y múltiples Eventos DML. Cuando este Evento es el último Evento de la declaración, se marcará con STMT_END_F, y es en este momento cuando se lanzará GLOBAL. READ LOCK, el código fuente es el siguiente:

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

Si la siguiente declaración comienza a obtener GLOBAL READ LOCK nuevamente, esto es lo que llamo acceso intermitente.


En este punto, las condiciones de estancamiento han madurado. Mientras se encuentre esta situación, puede ser necesaria la intervención humana para continuar.

Seis, sobre mysqldump

La edición comunitaria debe agregar FTWRL en las siguientes situaciones:

  • Establecer datos maestros

  • Establecer registros de transacciones individuales y de descarga

La versión percona necesita agregar FTWRL en las siguientes situaciones:

  • Establecer registros de transacciones individuales y de descarga

Echemos un vistazo a la versión comunitaria del código de la siguiente manera (versión de código 8.0.21), el siguiente es el proceso de vertido de UNLOCK desde FTWRL:

 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;

La función de juicio check_consistent_binlog_pos se agrega a la versión percona, de la siguiente manera (pero sin demasiada descripción):

  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;
  }

Siete, como resolver

Resumido como sigue:

  • Los datos maestros generalmente aumentan en las copias de seguridad, por lo que las copias de seguridad solo se pueden realizar durante los períodos pico bajos para minimizar el impacto.

  • Considere desactivar el parámetro slave_preserve_commit_order. Pero el bloqueo de FTWRL todavía existe, pero no provocará un punto muerto.

  • Si la presión no es grande, considere cerrar MTS. Pero el bloqueo de FTWRL todavía existe, pero no provocará un punto muerto.

Se acabó el texto completo.

Disfruta MySQL :)

Escanee el código para agregar el autor WeChat

La clase "MySQL Core Optimization" de Teacher Ye se ha actualizado a MySQL 8.0, escanee el código para comenzar el viaje de la práctica de MySQL 8.0

Supongo que te gusta

Origin blog.csdn.net/n88Lpo/article/details/111148053
Recomendado
Clasificación