###一、问题来源

这是一位客户的提供的案例如下,show processlist截图如下:

![image.png](https://upload-images.jianshu.io/upload_images/7398834-82e9b0bbd7244fbe.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

出现这种问题除非手动干预,杀掉FTWRL的session主从方可以进行。

版本社区版5.7.26

###二、堵塞图

如果分析上面的堵塞可以画图如下:

![未命名文件 (8).png](https://upload-images.jianshu.io/upload_images/7398834-8a20c1d9b1e3c384.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

##三、关于woker线程w1和w3的等待

这里我们需要重点关注参数slave_preserve_commit_order,在我将要出版的《深入理解MySQL主从原理》一书中做了详细描述,这里简单说明如下:

- 这个参数是为了保证从库group commit中的每个工作线程的事务提交顺序和主库事务执行的顺序一致。它在order commit的flush阶段前就生效。工作线程的事务在等待获取自己提交权限期间会堵塞在状态‘Waiting for preceding transaction to commit’下。

但是我们知道在order commit的flush之前就会获取MDL_key::COMMIT。因此这里w1和w3工作线程正在等待自己提交权限的到来,但是遗憾的是w2的事务由于不能获取global read lock而迟迟不能提交。同时它们堵塞了FTWRL。

###四、关于FTWRL的等待

这个我也多次描述过了,FTWRL的过程大概如下:

**第一步:** 加MDL LOCK类型为GLOBAL 级别为S。如果出现等待状态为‘Waiting for global read lock’。注意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 | + - - - |

```

**第二步:**推进全局表缓存版本。源码中就是一个全局变量 refresh_version++。

**第三步:**释放没有使用的table 缓存。可自行参考函数close_cached_tables函数。

**第四步:**判断是否有正在占用的table缓存,如果有则等待,等待占用者释放。等待状态为'Waiting for table flush'。这一步会去判断table缓存的版本和全局表缓存版本是否匹配,如果不匹配则等待如下:

```

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;

}

}

```

而等待的结束就是占用的table缓存的占用者释放,这个释放操作存在于函数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唤醒,也就是说对于正在占用的table缓存释放者不是FTWRL会话而是占用者自己。不管怎么样最终整个table缓存将会被清空,如果经过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;

}

```

**第五步:**加MDL LOCK类型COMMIT 级别为S。如果出现等待状态为‘Waiting for commit lock’。如果有大事务的提交很可能出现这种等待。

**注意**这里的第五步,正是因为w1和w3获取了MDL LOCK COMMIT,而又在等待w2的事务提交因此FTWRL也不得不等待。

###五、关于woker线程w2的等待

这里可能的原因有2个:

- 多线程并行的情况下,线程执行的顺序本生就是不定的,很可能线程由于丢失CPU而落后其他线程的处理,因为CPU调度的最小单位是线程。如果保证某个共享内存操作的完整性需要用到mutex、原子变量、内存屏障等技术。

- 如果w2中的事务本生就包含了多个DML语句,那么获取GLOBAL READ LOCK本生就是间歇性的,也就是每个语句结束都会释放,然后下一个语句开始的时候再次open table来获取。

我们来看看第二点,只考虑row_format格式的binlog。

我们知道一个事务可以包含多个语句,每条语句都会包含一个map Event和多个DML Event,当本Event是语句的最后一个Event的时候会使用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

```

如果下一条语句开始又会重新获取GLOBAL READ LOCK,这就是我说的间歇性获取。

---

到这里死锁条件已经成熟,只要遇到这种情况就可能需要人为介入才能继续了。

###六、关于mysqldump

社区版在如下情况下需要增加FTWRL:

- 设置了master-data

- 设置了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;

```

percona版本中增加了判断函数check_consistent_binlog_pos如下(不过多描述):

```

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;

}

```

###七、如何解决

总结如下:

- master-data 一般备份都会增加,因此只能在低峰期进行备份,尽量减少影响。

- 考虑关闭参数slave_preserve_commit_order。但是FTWRL的堵塞还是存在,但是不会产生死锁。

- 如果压力不大可以考虑关闭MTS。但是FTWRL的堵塞还是存在,但是不会产生死锁。

mysql 5.7 mts_MySQL:MTS和mysqldump死锁相关推荐

  1. mysql load会锁表吗_Mysql必读MySQL中由load data语句引起死锁的解决案例

    <MysqL必读MysqL中由load data语句引起死锁的解决案例>要点: 本文介绍了MysqL必读MysqL中由load data语句引起死锁的解决案例,希望对您有用.如果有疑问,可 ...

  2. mysql数据库备份及恢复命令mysqldump,source的用法

    还原一个数据库:mysql -h localhost -u root -p123456 www<c:/www.sql 备份一个数据库:mysqldump -h localhost -u root ...

  3. Mysql不能备份序列_无法mysqldump

    使用mysqldump工具备份还原Mysql数据库实例及参数详细说明 MySQL命令行功能非常强大,甚至可以进行数据库的备份,下面为您介绍的MySQL命令行就用于备份数据库,希望对您有所帮助. 注意, ...

  4. MYSQL中一个特殊的MDL LOCK死锁案列

    水平有限 如有错误请指出共同探讨 版本:5.7.19 mysql+innodb 本文中所说的上文是如下文章,也是讨论MDL LOCK死锁的基础,包含了很多MDL LOCK的基础知识建议 好好阅读 ht ...

  5. MySQL 唯一索引 UNIQUE KEY 会导致死锁?

    唯一性索引unique影响: 唯一性索引表创建: DROP TABLE IF EXISTS `sc`; CREATE TABLE `sc` (`id` int(11) NOT NULL AUTO_IN ...

  6. windows mysql dump_mysql在Windows下使用mysqldump命令手动备份数据库和自动备份数据库...

    手动备份: cmd控制台: 先进入mysql所在的bin目录下,如:cd C:\Program Files\MySQL\MySQL Server 5.5\bin mysqldump -u root - ...

  7. MySQL Replication--多线程复制MTS

    多线程复制 多线程复制MTS(Mult-Threaded Slave Applier)指使用多个线程来并发应用二进制日志. 在MYSQL5.6版本中,多线程复制基于schema来实现,将多个数据库下的 ...

  8. dump mysql database_【MySQL Database】数据迁移工具:mysqldump

    [mysql@wallet01 ~]$ mysqldump --help -u, --user=name 指定连接数据库服务器使用的用户 -p, --password 指定连接数据库服务器使用的密码 ...

  9. mysql中删除同一行会经常出现死锁?太可怕了

    之前有一个同事问到我,为什么多个线程同时去做删除同一行数据的操作,老是报死锁,在线上已经出现好多次了,我问了他几个问题: 1. 是不是在一个事务中做了好几件事情? 答:不是,只做一个删除操作,自动提交 ...

最新文章

  1. ajax 中Accordion控件的使用
  2. Linux网络编程 之 网络协议概述(一)
  3. 解读 Knative Eventing v0.10.0 最新版本特性
  4. java三次登录锁定_Java基础知识点有哪些 如何快速步入Java行业
  5. 数据库系统概论-第一章绪论
  6. OpenCV2:幼儿园篇 第七章 界面事件
  7. IT人员应该掌握的30种技能
  8. 近红外二区量子点CdTe/Zns,CdHgTe,CdTe/CdSe,CdS、CdSe、CdTe,ZnS、ZnSe偶联抗肿瘤药物阿霉素/紫杉醇/顺铂/喜树碱
  9. Windows 下 Spark+Hadoop+Scala 安装
  10. (转)UEFI系统的启动过程
  11. NODE_多文件上传
  12. linux CentOS7安装VCS、Verdi、SCL
  13. html自动缩放不出现滚动条,HTML页面缩小后显示滚动条的示例代码
  14. 自然语言处理不属于计算机科学,自然语言处理是一门融语言学、计算机科学和什么于一体的科学...
  15. Node版本的升级/降级
  16. 广州医保个账支付接口开发 微信医保支付
  17. 将B站上下载的两个m4s文件合成为mp4文件
  18. 圆为什么规定一定是360度_为什么圆是360度
  19. Word2010中自动尾注添加参考文献
  20. 信息学奥赛C++编程:奥运奖牌计数

热门文章

  1. phpstorm failed to create jvm:error code -6 解决办法 解决方法
  2. ubuntu下nginx+php5的部署
  3. Windows 2008
  4. Signal和slot的声明和连接
  5. centos7 JDK1.8
  6. Django restful-framework初步学习
  7. Linux--文件管理以及权限的修改
  8. OneAPM挂牌新三板,续写 ITOM 新篇章
  9. Docker 安装registry (构建私有镜像库)
  10. array_multisort