作者:王福祥

爱可生 DBA 团队成员,负责客户的数据库故障处理以及调优。擅长故障排查及性能优化。对数据库相关技术有浓厚的兴趣,喜欢分析各种逻辑。

本文来源:原创投稿

*爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。


MySQL 主从复制功能可以搭建从库来为 MySQL 创建一套在线的备份系统,但是自身不能独立实现切换;需要借助第三方高可用工具。然而当自身有大事务在运行时会阻塞一些 show 语句;例如“show master status”,造成误判。

场景模拟

1、构造两千万行数据,以事务的形式删除

2、新建会话执行 show master status ,在 sql 语句运行期间执行成功。在 commit 期间被阻塞。

3、show master status 处于 starting 状态,也就是语句开始执行的第一个阶段,启动阶段,准备资源。

4、查看 stack 信息,show master status 是在获取 lock_log 锁时被阻塞

#6  0x0000000000ee8278 in MYSQL_BIN_LOG::get_currrent_log (this=0x1e839c0 <mysql_bin_log>, linfo=0x7f3ea82e62d0, need_lock_log=<optimized out>) at /export/home/pb2/build/sb_0-32013917-1545390211.74/mysql-5.7.25/sql/binlog.cc:5514......#6  0x0000000000eef824 in MYSQL_BIN_LOG::change_stage (this=<optimized out>, thd=<optimized out>, stage=<optimized out>, queue=<optimized out>, leave_mutex=<optimized out>, enter_metux=0x1e839c8 <mysql_bin_log+8>) at /export/home/pb2/build/sb_0-32013917-1545390211.74/mysql-5.7.25/sql/binlog.cc:9170

5、最终因事务过大,最终 show master status 超时,导致了故障切换。

其原因为 commit 与 show master status 之间的阻塞等待现象,接下来分析一下原因。

原因分析:commit

commit 流程分为以下几个阶段(以5.7.25为例),为原子性操作,一次性写入。

1、prepare 阶段主要作用为刷 redo 、undo ;此时 binlog 只涉及到一些准备动作。参照 binlog_prepare 函数。

static int binlog_prepare(handlerton *hton, THD *thd, bool all){  DBUG_ENTER("binlog_prepare");  if (!all)  {    thd->get_transaction()->store_commit_parent(mysql_bin_log.      m_dependency_tracker.get_max_committed_timestamp());  }

2、flush 阶段主要作用为持久化 redo ;获取 lock_log 锁阻塞其他组事务写入,以及生成 flush 队列(第一个事务为 leader ,随后为 follower )以及写入 binlog ,此时是内存写但不刷盘,参照 process_flush_stage_queue 函数

intMYSQL_BIN_LOG::process_flush_stage_queue(my_off_t *total_bytes_var,                                         bool *rotate_var,                                         THD **out_queue_var){  DBUG_ENTER("MYSQL_BIN_LOG::process_flush_stage_queue");  #ifndef DBUG_OFF  // number of flushes per group.  int no_flushes= 0;  #endif  DBUG_ASSERT(total_bytes_var && rotate_var && out_queue_var);  my_off_t total_bytes= 0;  int flush_error= 1;  mysql_mutex_assert_owner(&LOCK_log);

3、sync 阶段作用为释放 lock_log 锁,获取 lock_sync 锁阻塞其他事务组刷盘,并根据 sync_binlog 决定刷盘策略。

 if (change_stage(thd, Stage_manager::SYNC_STAGE, wait_queue, &LOCK_log, &LOCK_sync))  {    DBUG_PRINT("return", ("Thread ID: %u, commit_error: %d",                          thd->thread_id(), thd->commit_error));    DBUG_RETURN(finish_commit(thd));  }

4、commit 阶段释放 lock_sync 锁,获取 lock_commit 锁并等待 follower 事务提交,提交后释放该锁以及清空 binlog 缓存以及判断是否刷新并归档 binlog

原因分析:show master status

show master status 其作用为从最后一份 binlog 文件中获取 Executed_Gtid 信息以及该 binlog 所执行的gtid信息。其函数入口为:https://github.com/mysql/mysql-server/blob/mysql-5.7.25/sql/rpl_master.cc 624行并获取 lock_log 锁:

int MYSQL_BIN_LOG::get_current_log(LOG_INFO* linfo, bool need_lock_log/*true*/){  if (need_lock_log)    mysql_mutex_lock(&LOCK_log);  int ret = raw_get_current_log(linfo);  if (need_lock_log)    mysql_mutex_unlock(&LOCK_log);  return ret;}

结论

案例中的例子是单个大事务,因此不涉及组提交的 sync 阶段,从 commit(flush 阶段)以及 show master status 的步骤中可以看出,两步操作之间的阻塞原因为 lock_log 锁的争用引起的,该锁的性质为 lock_log 日志锁,而并非MDL以及引擎层锁,如果事务越大,flush 阶段写入 binlog 期间以及持有锁的时间就会越长;从而阻塞其他事务提交以及 binlog 的 show 操作,因此对应的解决方案为:

1、大事务会造成io暴涨、主从延时以及事务阻塞等问题,在 MGR 环境中甚至会造成复制中断,mysql 需要避免大事务。

2、binlog 是持续在变化的文件,show master status 语句可改为 select @@global.gtid_executed ;来获取 GTID 信息。


avatar
100
  Subscribe  
提醒