故障分析 | 全局读锁一直没有释放,发生了什么?

别有洞天
• 阅读 1722
作者:刘开洋
爱可生交付服务部团队北京 DBA,主要负责处理 MySQL 的 troubleshooting 和我司自研数据库自动化管理平台 DMP 的日常运维问题,对数据库及周边技术有浓厚的学习兴趣,喜欢看书,追求技术。
本文来源:原创投稿
* 爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。

问题:

在一个客户的线上监控告警中,提示主从延迟不断升高,我们登上数据库进行查看一下,发现 MySQL 从库复制状态提示 SQL 线程在 waiting for global read lock。

故障分析 | 全局读锁一直没有释放,发生了什么?

在数据库的进程列表中发现了存在的等待全局读锁和 kill slave 的进程;高可用在不断重启复制,原因是由于 NAT 网络中域名反解析出错导致高可用软件对复制的误判。

故障分析 | 全局读锁一直没有释放,发生了什么?

这里就能看出两个问题,第一是有个下发全局读锁的对象,一般在从库上就是备份工具了,第二就是 slave 正在被 kill,而且时间相当长,因此这里可能存在一种特殊的死锁。

查看 mysql 进程时的巧合下,发现 mysqldump 进程已存在 10 多个小时,对比等 FTWRL 的进程的时间,就坐实了下发全局读锁的对象是 mysqldump:

故障分析 | 全局读锁一直没有释放,发生了什么?

线上没有开启 performance_schema 的 instruments 和 consumers(PS:这个对于锁监控很重要,一定记得打开)。如果开启了 performance_schema,可以通过 metadata_locks 查到相关锁记录,这个我们在后面的复现中看一下。

故障分析 | 全局读锁一直没有释放,发生了什么?

上述情况分析得出存在一个特殊的死锁,形成 MySQL Server 层和存储引擎层的死锁闭环,而且不能完全追踪到所有锁记录。

解决:

这样三个锁组合成的死锁在其他客户端执行 UNLOCKS TABLE 是解不开的,只需要 kill 掉全局读锁或者等待全局锁的锁一个即可,因为没有找到全局锁对应的线程,这里将等全局锁的线程 kill 掉,数据库就恢复了,再看残留的 mysqldump 进程消失了:

故障分析 | 全局读锁一直没有释放,发生了什么?

两个锁就此解开了

故障分析 | 全局读锁一直没有释放,发生了什么?

故障恢复,延迟追平。

Review:为什么 stop slave 和 FTWRL 会发生死锁?

在 mysql 5.7.26 中执行 mysqldump 加参数 master_data、single_transaction 和 flush_logs 得出的 general log 中发现

mysqldump执行的general log
root@127.0.0.1 on  using TCP/IP
/*!40100 SET @@SQL_MODE='' */
/*!40103 SET TIME_ZONE='+00:00' */
FLUSH /*!40101 LOCAL */ TABLES
FLUSH TABLES WITH READ LOCK                                       # 加全局读锁
Refresh                                                           # 刷新日志
  
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ           # 设置设置会话级别RR
START TRANSACTION /*!40100 WITH CONSISTENT SNAPSHOT */          
SHOW MASTER STATUS                                                # 获取master的状态
UNLOCK TABLES                                                     # 解锁表
···
Quit

场景复现:

故障分析 | 全局读锁一直没有释放,发生了什么?

[root@localhost ~]# gdb -ex "set pagination 0" -ex "thread apply all bt" --batch -p 13192   # 打印对应MySQL进程堆栈——找到stop slave卡住的线程后处理输出
······
Thread 23 (Thread 0x7f98f027e700 (LWP 19446)):
#0  pthread_cond_timedwait@@GLIBC_2.3.2 ()
#1  native_cond_timedwait
#2  my_cond_timedwait
#3  inline_mysql_cond_timedwait
#4  terminate_slave_thread (thd=0x7f98740008c0, term_lock=0x7f9884022908, term_cond=0x7f9884022a08, slave_running=0x7f9884022ac4, stop_wait_timeout=0x7f98f027c448, need_lock_term=false) at /export/home/pb2/build/sb_0-33648028-1555164244.06/mysql-5.7.26/sql/rpl_slave.cc:1861
#5  terminate_slave_threads (mi=0x7f988401c500, thread_mask=3, stop_wait_timeout=<optimized out>, need_lock_term=false) at /export/home/pb2/build/sb_0-33648028-1555164244.06/mysql-5.7.26/sql/rpl_slave.cc:1671
#6  stop_slave (thd=0x7f987c00f650, mi=0x7f988401c500, net_report=true, for_one_channel=false, push_temp_tables_warning=0x7f98f027c52f) at /export/home/pb2/build/sb_0-33648028-1555164244.06/mysql-5.7.26/sql/rpl_slave.cc:10261
#7  stop_slave (thd=0x7f987c00f650) at /export/home/pb2/build/sb_0-33648028-1555164244.06/mysql-5.7.26/sql/rpl_slave.cc:615
#8  stop_slave_cmd
#9  mysql_execute_command
#10 mysql_parse
#11 dispatch_command
#12 do_command
#13 handle_connection
#14 pfs_spawn_thread
#15 start_thread ()
#16n clone ()
  
# 可以看到# 5 stop_wait_timeout,获取更多相关信息
(gdb) thread 34
[Switching to thread 34 (Thread 0x7f58d661f700 (LWP 13240))]
#0  0x00007f592d8e0d42 in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
(gdb) f 5
#5  0x0000000000f0ed57 in terminate_slave_threads (mi=0x7f587801f050, thread_mask=3, stop_wait_timeout=<optimized out>, need_lock_term=false)
    at /export/home/pb2/build/sb_0-32013917-1545390211.74/mysql-5.7.26/sql/rpl_slave.cc:1671
warning: Source file is more recent than executable.
1671        if ((error=terminate_slave_thread(mi->rli->info_thd, sql_lock,
(gdb) print mi->rli->info_thd
$1 = (THD *) 0x7f58700008c0
 
  
Thread 21 (Thread 0x7f58c05a6700 (LWP 14238)):
#9  apply_event_and_update_pos (ptr_ev=0x7f58c05a56c8, thd=0x7f58700008c0, rli=0x7f5878024b30)
#10 exec_relay_log_event (thd=0x7f58700008c0, rli=0x7f5878024b30)
  
# 通过gdb输出看到stop slave持有mi->rli->info_thd,在等待当前会话地址为0x7f58700008c0,定位到线程21的apply_event_and_update_pos 和exec_relay_log_event函数执行
apply_event_and_update_pos:应用给定事件并提高relay log的位置。
exec_relay_log_event:这是从SQL线程调用于执行relay log中下一个事件的顶级功能。
mi=master info, rli=relay log info

故障分析 | 全局读锁一直没有释放,发生了什么?

故障分析 | 全局读锁一直没有释放,发生了什么?

terminate_slave_thread 函数:等 SQL thread 终止的函数。该函数在请求线程终止后被调用(通过将 Relay_log_info 或 Master_info 配置中的 abort_slave 设置为 1),线程的终止由 *slave_running 控制。函数将在等待条件之前获取 term_lock,need_lock_term 为 false 的情况下,mutex 应该属于这个函数的调用者,在函数返回之后一直保持获取 mutex 的状态。

故障分析 | 全局读锁一直没有释放,发生了什么?

mysqldump 备份期间出现的操作是:

  • master-data=2 会结合 single-transaction 给数据库加一个全局读锁。
  • flush-logs 是要对 relay log 进行 refresh。

STOP SLAVE 在备份期间做的操作是:

  • STOP SLAVE 会等待 IO 线程结束,然后释放 LOCK_msp_map 和占有的 master_info

流程:

  • mysqldump 备份进行 FTWRL 之后恰巧遇到 HA 执行 STOP SLAVE,SQL Thread 在 STOP 之前持有 mi->stop_cond 锁,commit 的时候等待 MDL_COMMIT,FTWRL 之后执行的 flush logs 时 reflresh 下发了一个系统锁,它是在等待 mi->stop_cond 的释放,因为 FTWRL 和 FLUSH LOGS 是一个程序发出的,所有从逻辑上讲 mysqldump 自己是在等待自己释放资源,便陷入了无限等待中,形成了我们看到的死锁。

关于 LOCK_msp_map 锁有兴趣的同学可以自行谷歌搜索

参考:
https://bugs.mysql.com/bug.ph...

https://bugs.mysql.com/file.p...

https://www.percona.com/blog/...

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Stella981 Stella981
3年前
Python3:sqlalchemy对mysql数据库操作,非sql语句
Python3:sqlalchemy对mysql数据库操作,非sql语句python3authorlizmdatetime2018020110:00:00coding:utf8'''
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
MySQL锁
<br1\.表锁表锁分为写锁,读锁,二者读读不阻塞,读写阻塞,写写阻塞<br<br2\.行锁行锁分为共享锁,排他锁,即读锁和写锁多粒度锁机制自动实现表、行锁共存,InnoDB内部有意向表锁意向共享锁(IS):事务在给一个数据行加共享锁前必须先取得该表的IS锁。
Stella981 Stella981
3年前
DOIS 2019 DevOps国际峰会北京站来袭~
DevOps国际峰会是国内唯一的国际性DevOps技术峰会,由OSCAR 联盟指导、DevOps时代社区与高效运维社区联合主办,共邀全球80余名顶级专家畅谈DevOps体系与方法、过程与实践、工具与技术。会议召开时间:2019070508:00至2019070618:00结束会议召开地点:北京主办单位:DevOps
Stella981 Stella981
3年前
HTML5新增input标签属性
一.inputtype属性1<formaction""2邮箱<inputtype"email"name""id""<inputtype"submit"value"提交"<br/<br/3手机号码<inputtype"tel"name
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Vitess全局唯一ID生成的实现方案 | 京东云技术团队
为了标识一段数据,通常我们会为其指定一个唯一id,比如利用MySQL数据库中的自增主键。但是当数据量非常大时,仅靠数据库的自增主键是远远不够的,并且对于分布式数据库只依赖MySQL的自增id无法满足全局唯一的需求。因此,产生了多种解决方案,如UUID,Sn
美凌格栋栋酱 美凌格栋栋酱
5个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
别有洞天
别有洞天
Lv1
黄鹂啭处谁同听,白菊开时且剩过。
文章
4
粉丝
0
获赞
0