为什么你的 show slave status 会卡住?
在数据库 MySQL 的日常运维中,生产环境一般都是 1 主 N 从的高可用架构。涉及主从同步的问题,通常执行 show slave status 命令就可以了解主从实例之间的同步状态,但是凡事总会有意外。
作者:徐文梁,爱可生 DBA 成员,公众号[DBA修行之路]主理人。一个执着于技术的数据库工程师,主要负责数据库日常运维工作。擅长 MySQL,Redis 等数据库。喜欢垂钓,看风景,结交新朋友,看书,技术总结。
爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。
本文约 3000 字,预计阅读需要 8 分钟。
问题背景
在数据库 MySQL 的日常运维中,生产环境一般都是 1 主 N 从的高可用架构。涉及主从同步的问题,通常执行 show slave status
命令就可以了解主从实例之间的同步状态,但是凡事总会有意外。
最近在生产环境中遇到 show slave status
命令执行卡住了的请看。以前在测试环境也遇到过,但是并没有深究,但这次是被客户问到了,发现自己说不清楚。如果此时此刻正在阅读本文的读者朋友您也不说清楚,那请随我去源码中一探究竟吧!
问题分析
执行 show slave status
卡住,为了更全面地了解 MySQL 的状态,通过 pstack 拿到了相应的线程信息。这里保留关键信息,如下:
Thread 6 (Thread 0xxxx (LWP xxx)): #0 0x..... in __lll_lock_wait () from /lib64/libpthread.so.0 #1 0x..... in _L_lock_975 () from /lib64/libpthread.so.0 #2 0x..... in pthread_mutex_lock () from /lib64/libpthread.so.0 #3 0x..... in inline_mysql_mutex_lock (that=0x1358520 <LOCK_active_mi>, src_file=<optimized out>, src_line=<optimized out>) at .../mysql-5.6.40/include/mysql/psi/mysql_thread.h:688 #4 0x..... in mysql_execute_command (thd=thd@entry=0x5d0abb0) at .../mysql-5.6.40/sql/sql_parse.cc:2853 #5 0x..... in mysql_parse (thd=thd@entry=0x5d0abb0, rawbuf=<optimized out>, length=<optimized out>, parser_state=parser_state@entry=0x7fe44c644690) at .../mysql-5.6.40/sql/sql_parse.cc:6453 #6 0x..... in dispatch_command (command=<optimized out>, thd=0x5d0abb0, packet=0x5d0def1 "show slave status", packet_length=17) at .../mysql-5.6.40/sql/sql_parse.cc:1374
通过上面的信息可知,卡住是因为 show slave status
在获取某种 mutex 锁 的时候被阻塞住所导致的。我们根据这个结论准备在测试环境中模拟和分析。
测试环境
客户的 MySQL 版本比较旧,所以自己准备了 MySQL 5.7.41 的 debug 环境。后续的测试分析都基于该版本,其他版本可能存在差异,但分析过程类似。
源码分析
执行 show slave status
时需要哪些 mutex 锁?我们去源码中看看 show slave status
的执行逻辑。
当我们在客户端执行 show slave status
命令后,经过一系列兜兜转转,会来到 mysql_execute_command
函数,进入 SQLCOM_SHOW_SLAVE_STAT
分支,对应文件为 mysql-5.7.41\sql\sql_parse.cc。
case SQLCOM_SHOW_SLAVE_STAT: { /* Accept one of two privileges */ if (check_global_access(thd, SUPER_ACL | REPL_CLIENT_ACL)) goto error; res= show_slave_status_cmd(thd); break; }
show_slave_status_cmd
函数是 show slave status
命令的入口,可以看出会涉及 channel_map 锁 ,对应文件为 mysql-5.7.41\sql\rpl_slave.cc。
bool show_slave_status_cmd(THD *thd) { Master_info *mi= 0; LEX *lex= thd->lex; bool res; ...... channel_map.rdlock(); if (!lex->mi.for_channel) res= show_slave_status(thd); else { ...... res= show_slave_status(thd, mi); } channel_map.unlock(); ...... }
show_slave_status
函数负责执行 show slave status
语句,可以看出涉及 global_sid_lock 锁 ,对应文件为 mysql-5.7.41\sql\rpl_slave.cc。
bool show_slave_status(THD* thd, Master_info* mi) { ...... if (mi != NULL) { global_sid_lock->wrlock(); ...... global_sid_lock->unlock(); } ...... show_slave_status_metadata(field_list, io_gtid_set_size, sql_gtid_set_size); ...... if (mi != NULL && mi->host[0]) { if (show_slave_status_send_data(thd, mi,io_gtid_set_buffer, sql_gtid_set_buffer)) ...... }
show_slave_status_send_data
函数将数据发送到 Master_info 的客户端,这部分涉及的锁相对较多,对应文件为 mysql-5.7.41\sql\rpl_slave.cc。
bool show_slave_status_send_data(THD *thd, Master_info *mi,char* io_gtid_set_buffer,char* sql_gtid_set_buffer) { ...... mysql_mutex_lock(&mi->info_thd_lock); ...... mysql_mutex_unlock(&mi->info_thd_lock); mysql_mutex_lock(&mi->rli->info_thd_lock); ...... mysql_mutex_unlock(&mi->rli->info_thd_lock); mysql_mutex_lock(&mi->data_lock); mysql_mutex_lock(&mi->rli->data_lock); mysql_mutex_lock(&mi->err_lock); mysql_mutex_lock(&mi->rli->err_lock); ...... mysql_mutex_unlock(&mi->rli->err_lock); mysql_mutex_unlock(&mi->err_lock); mysql_mutex_unlock(&mi->rli->data_lock); mysql_mutex_unlock(&mi->data_lock); }
show_master_status
函数负责执行 show master status
语句,可以看出涉及 global_sid_lock 锁 ,对应文件为 mysql-5.7.41\sql\rpl_slave.cc。
bool show_master_status(THD* thd) { ...... global_sid_lock->wrlock(); ...... global_sid_lock->unlock(); ...... }
关于主从复制中的 mutex 锁 的功能及涉及到该锁的大部分操作,官方还是很贴心的给出了详细的解释,就不一一赘述了。对应文件为 mysql-5.7.41\sql\rpl_slave.h(略)。
模拟场景
直接模拟 show slave status
卡住的场景不太容易,但是通过 debug+断点调试可以轻松实现。
从前文源码分析可知,show slave status
命令和 show master status
命令执行过程中都涉及到获取 global_sid_lock 锁 ,因此可以在 show master status
命令对应的函数中获取 global_sid_lock 锁 (此时还没释放的中间位置设置断点),然后执行 show slave status
命令时获取 global_sid_lock 锁 便会被阻塞。另外,可以同步在释放 global_sid_lock 锁 函数结束之前设置断点,形成对比。详细流程及步骤如下:
-
源码编译 debug 模式,启动 MySQL 服务,并启动两个会话。
#查看 MySQL 进程 [root@localhost ~]# ps -ef|grep mysql root 14949 14919 0 23:13 pts/3 00:00:00 grep --color=auto mysql mysql 31658 1 0 06:52 ? 00:01:14 /root/mysql-5.7.41/build/sql/mysqld --defaults-file=/etc/my.cnf --user=mysql
#会话 A mysql> show processlist; +----+------+-----------+------+---------+------+----------+------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+------+-----------+------+---------+------+----------+------------------+ | 4 | root | localhost | NULL | Query | 0 | starting | show processlist | | 5 | root | localhost | NULL | Sleep | 9 | | NULL | +----+------+-----------+------+---------+------+----------+------------------+ 2 rows in set (0.00 sec)
#会话 B mysql> show processlist; +----+------+-----------+------+---------+------+----------+------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+------+-----------+------+---------+------+----------+------------------+ | 4 | root | localhost | NULL | Sleep | 154 | | NULL | | 5 | root | localhost | NULL | Query | 0 | starting | show processlist | +----+------+-----------+------+---------+------+----------+------------------+ 2 rows in set (0.00 sec)
#查看 thread 表 mysql> select thread_id,processlist_id ,thread_os_id, name from performance_schema.threads where name="thread/sql/one_connection"; +-----------+----------------+--------------+---------------------------+ | thread_id | processlist_id | thread_os_id | name | +-----------+----------------+--------------+---------------------------+ | 30 | 4 | 16055 | thread/sql/one_connection | | 31 | 5 | 16090 | thread/sql/one_connection | +-----------+----------------+--------------+---------------------------+ 2 rows in set (0.00 sec)
-
gdb 关联 MySQL 进程并设置断点。
#关联 MySQL 进程 Type "apropos word" to search for commands related to "word". (gdb) show non-stop Controlling the inferior in non-stop mode is off. (gdb) set non-stop on (gdb) show non-stop Controlling the inferior in non-stop mode is on. (gdb) attach 31658 Attaching to process 31658
#查看相关线程 (gdb) info threads Id Target Id Frame
- 1 Thread 0x2b0ca2618480 (LWP 31658) "mysqld" 0x00002b0ca4113ddd in poll () from /lib64/libc.so.6 ...... 31 Thread 0x2b0caff6a700 (LWP 16055) "mysqld" (running) 32 Thread 0x2b0caff28700 (LWP 16090) "mysqld" (running)
#切换到指定线程,对应会话 A (gdb) t 31 Switching to thread 31 (Thread 0x2b0caff6a700 (LWP 16055))
#设置断点,持有锁而不释放 (gdb) b rpl_master.cc:647 Breakpoint 1 at 0x181a565: file /root/mysql-5.7.41/sql/rpl_master.cc, line 647. #设置断点,释放锁 (gdb) b rpl_master.cc:649 Breakpoint 2 at 0x181a577: file /root/mysql-5.7.41/sql/rpl_master.cc, line 649. #查看断点 (gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x000000000181a565 in show_master_status(THD*) at /root/mysql-5.7.41/sql/rpl_master.cc:647 2 breakpoint keep y 0x000000000181a577 in show_master_status(THD*) at /root/mysql-5.7.41/sql/rpl_master.cc:649
-
会话 B 执行
show slave status
命令。#结果很快返回,不会被阻塞 mysql> show slave status; Empty set (0.00 sec)
-
先在会话 A 执行
show master status
命令,然后在会话 B 执行show slave status
命令。#会话 A 执行 "show master status" 命令卡住 mysql> select now(); +---------------------+ | now() | +---------------------+ | 2024-08-02 23:55:02 | +---------------------+ 1 row in set (0.00 sec)
mysql> show master status;
#会话 B 执行 "show slave status" 命令卡住 mysql> select now(); +---------------------+ | now() | +---------------------+ | 2024-08-02 23:55:18 | +---------------------+ 1 row in set (0.00 sec)
mysql> show slave status;
#会话 A 触发断点一 Thread 31 "mysqld" hit Breakpoint 1, show_master_status (thd=0x2b0e040009a0) at /root/mysql-5.7.41/sql/rpl_master.cc:648 648 global_sid_lock->unlock();
Num Type Disp Enb Address What 1 breakpoint keep y 0x000000000181a565 in show_master_status(THD*) at /root/mysql-5.7.41/sql/rpl_master.cc:647 breakpoint already hit 1 time 2 breakpoint keep y 0x000000000181a577 in show_master_status(THD*) at /root/mysql-5.7.41/sql/rpl_master.cc:649
-
gdb 调试继续运行会话 A,观察会话 A 和会话 B 状态。
#继续运行,会话 A 触发断点二 (gdb) c Continuing.
Thread 31 "mysqld" hit Breakpoint 2, show_master_status (thd=0x2b0e040009a0) at /root/mysql-5.7.41/sql/rpl_master.cc:650 650 field_list.push_back(new Item_empty_string("File", FN_REFLEN));
#观察会话 A,"show master status"命令继续卡住 mysql> select now(); +---------------------+ | now() | +---------------------+ | 2024-08-02 23:55:02 | +---------------------+ 1 row in set (0.00 sec)
mysql> show master status;
#观察会话 B,"show show status"命令返回 mysql> select now(); +---------------------+ | now() | +---------------------+ | 2024-08-02 23:55:18 | +---------------------+ 1 row in set (0.00 sec)
mysql> show slave status; Empty set (4 min 13.76 sec)
#会话 B 继续执行 "show show status" 命令都立即返回 mysql> select now(); +---------------------+ | now() | +---------------------+ | 2024-08-03 00:03:53 | +---------------------+ 1 row in set (0.00 sec)
mysql> show slave status; Empty set (0.00 sec)
mysql> show slave status; Empty set (0.00 sec)
#继续运行,观察会话 A,因为后面没有其他断点,所以成功返回 mysql> show master status; +------------------+----------+--------------+------------------+-------------------+ | File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set | +------------------+----------+--------------+------------------+-------------------+ | mysql-bin.000044 | 154 | | | | +------------------+----------+--------------+------------------+-------------------+ 1 row in set (9 min 56.99 sec)
mysql> select now(); +---------------------+ | now() | +---------------------+ | 2024-08-03 00:05:34 | +---------------------+ 1 row in set (0.00 sec)
注意:如果断点设置在 show master status
命令获取 global_sid_lock 锁 之前或者释放 global_sid_lock 锁 之后则无法阻塞 show slave status
命令。
总结
通过代码和试验,对 show slave status
命令有了更清晰的了解。可能导致 show slave status
卡住的场景很多,例如前面的模拟测试,无法穷举。类似问题可以从以下方向进行排查:
-
执行
show slave status
过程中会需要获取channel_map.rdlock()
,global_sid_lock->wrlock
,mi->data_lock
等相关 mutex 锁 。如果此时这些锁中的一个或多个锁被其他线程持有,show slave status
就会阻塞。 -
当
show slave status
命令执行阻塞时,可以借助 pstack 工具和performance_schema.threads
(MySQL 5.7 之前的版本不支持thread_os_id
字段)表保留当时信息,便于后期进行排查,分析show slave status
命令阻塞的位置点。
更多技术文章,请访问:https://opensource.actionsky.com/
关于 SQLE
SQLE 是一款全方位的 SQL 质量管理平台,覆盖开发至生产环境的 SQL 审核和管理。支持主流的开源、商业、国产数据库,为开发和运维提供流程自动化能力,提升上线效率,提高数据质量。
✨ Github:https://github.com/actiontech/sqle
📚 文档:https://actiontech.github.io/sqle-docs/
💻 官网:https://opensource.actionsky.com/sqle/
👥 微信群:请添加小助手加入 ActionOpenSource

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
极客与技术,产业与生态,年度开源峰会 2024 GOTC x GOGC 即将开幕
8 月 15 日至 16 日,2024全球开源技术峰会 GOTC x GOGC 全球开源极客嘉年华将于上海张江科学会堂举行。 全球开源技术峰会(Global Open-source Technology Conference,简称 GOTC)始于 2021 年,是面向全球开发者的开源技术盛会。 GOTC 2024 与上海浦东软件园联合举办,并结合“GOTC(全球开源技术峰会)”与“GOGC(全球开源极客嘉年华)”,旨在打造一场全新开源盛会。2024 全球开源极客嘉年华(GOGC 2024)由浦东软件园携手 S 创共建,与开源中国、Linux 基金会等品牌联合呈现。 此次大会将集结全球范围内对开源技术充满热情的开发者、社区成员、创业者、企业领袖、媒体人,以及各开源项目应用场景的产业精英、跨界才俊与年轻力量。 通过主题演讲、圆桌讨论、创新集市、人才集市、黑客松、技术展示和互动工作坊等形式,与会者将有机会交流实践经验、探索前沿技术,让我们一起激发创新活力、展示开源魅力、促进跨领域合作。 大会论坛 8 月 15-16 日,上海・张江科学会堂,100+ 来自不同公司、不同社区、不同领域的重磅嘉宾...
- 下一篇
国内首个支持国产化信创的开源云原生平台
国产化信创是指中国本土信息技术和创新产业的发展和推广。随着各种形势的复杂变化,推动国产化和信创已成为信息产业发展的重要方向。在这一背景下,国内的技术企业和开发者们纷纷投入到开源国产化和自主创新的浪潮中,力图摆脱对国外技术和服务的依赖。从硬件到软件,再到云原生。 众所周知,在各个技术领域都有国产化信创的产品,比如国产CPU、国产操作系统、国产数据库等等都有厂商在做,都有开源的版本。但目前开源的国产化信创云原生平台目前较少。据我了解,目前在国内 Rainbond 是首个支持国产化信创的开源云原生平台。 国产化信创环境支持 目前主流的国产化 CPU 厂商包括飞腾、华为、龙芯、海光、兆芯等,其指令集集中在 X86 、Arm 以及自主性极高的 LoongArch (MIPS 指令集的后继者) 。 Rainbond 开源版本对国产 CPU 和国产操作系统提供全面支持,确保应用能够在国产硬件和软件环境下稳定运行。这包括对多种国产 CPU 架构的优化和适配,如鲲鹏、飞腾、龙芯等,以及对国产操作系统的兼容性,例如统信、银河麒麟、中标麒麟、龙蜥、欧拉操作系统等。这种支持不仅涵盖了基础的运行环境,还包括了对...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8安装Docker,最新的服务器搭配容器使用
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- 设置Eclipse缩进为4个空格,增强代码规范
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7设置SWAP分区,小内存服务器的救世主