MySQL 5.7中sync_binlog参数和半同步中after_commit和after_sync的区别

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介:

本文为我的一些零散记录供以后参考,本来知道已经很久了但是有朋友问到老是需要翻很久,这里干脆记录下来,但是水平有限都不深入,如有误导请见谅为什么将他们放在一起讨论因为他们都存在于同一个函数MYSQL_BIN_LOG::ordered_commit函数中。

代码版本:percona 5.7.14

以下讨论sync_binlog参数在5.7中的作用

一、sync_binlog参数设置在源码中的表示

这个参数大家都知道控制着binlog的刷盘时机,但是在5.7中其还有另外一个功能,我这里将解释他的两个功能。我摘取了源码中说明问题的部分进行展示如下:

  • flush阶段:
flush_error= process_flush_stage_queue(&total_bytes, &do_rotate,&wait_queue);//进行binlog的从binlog buffer或者临时文件写入到binlog文件(注意是写到kernel buffer还没做fsync),同时触发innodb的组提交逻辑,innodb组提交的逻辑代码是阿里的印风兄写的,我请教过他。
update_binlog_end_pos_after_sync= (get_sync_period() == 1);//sync_binlog参数 如果为1则为真如果不为1则为假
    if (!update_binlog_end_pos_after_sync)//如果sync_binlog=1则 这里不发信号给dump 如果不是1则发信号进行dump
      update_binlog_end_pos();

其中get_sync_period()函数返回就是sync_binlog的设置,这里能够清晰看到如果sync_binlog != 1才会 在flush阶段发送信号给dump线程。

  • sync阶段
if (flush_error == 0 && total_bytes > 0) //这里进行sync binlog,
  {
    DEBUG_SYNC(thd, "before_sync_binlog_file");
    std::pair<bool, bool> result= sync_binlog_file(false);
    sync_error= result.first;
  }

  if (update_binlog_end_pos_after_sync) //如果sync_binlog = 1 这里才发送信号给dump线程通知进行发送binlog
  {
    THD *tmp_thd= final_queue;

    while (tmp_thd->next_to_commit != NULL)
      tmp_thd= tmp_thd->next_to_commit;
    if (flush_error == 0 && sync_error == 0)
      update_binlog_end_pos(tmp_thd->get_trans_pos());
  }

如果我们翻开sync_binlog_file函数的逻辑会发现这样一个逻辑:

if (force || (sync_period && ++sync_counter >= sync_period))
  {
    sync_counter= 0;

很显然这里有一个计数器sync_counter,如果当sync_binlog>1的时候才,等到sync_counter大于你设置的sync_binlog的值的时候才会触发fsync binlog(注意这里是++sync_counter 先自增再比较),这里也解释了sync_binlog>1的时候代表的是什么值,代表是组提交的次数。

二、sync_binlog参数在5.7中作用的总结
  • sync_binlog=0:binlog不FSYNC刷盘,依赖于OS刷盘机制,同时dump线程会在flush阶段后进行binlog传输
  • sync_binlog=1:binlog进行FSYNC刷盘,同时dump线程会在sync阶段后进行binlog传输
  • sync_binlog>1:binlog将在指定次数组提交后FSYNC刷盘,同时dump线程会在flush阶段后进行binlog传输
三、为什么这么修改

这也是一个朋友问我的问题,如果主库异常重启后,从库是否有比主库多事物的风险,实际上这个问题就是到底在什么阶段后dump线程进行传输binlog的问题。实际上如果在flush阶段过后传输确实可能出现这个问题,而在sync阶段后传输这个时候binlog已经落盘了,就不会有这种风险了。如果出现这种错误会报错如下,这个错误也是有朋友遇到过的:

ER_SLAVE_HAS_MORE_GTIDS_THAN_MASTER 
"Slave has more GTIDs than the master has, using the master's SERVER_UUID. This may indicate that the end of the binary log was truncated or that the last binary log file was lost, e.g., after a power or disk failure when sync_binlog != 1. The master may or may not have rolled back transactions that were already replicated to the slave. Suggest to replicate any transactions that master has rolled back from slave to master, and/or commit empty transactions on master to account for transactions that have been committed on master but are not included in GTID_EXECUTED." 

下面开始讨论半同步中after_commit和after_sync的区别,这个问题一直也是大家讨论的重点。

四、从一个问题出发讨论

也是有很多朋友问我,其中一个问题如下:

半同步:after_sync模式
测试:超时时间设置为1个小时不让主库切换异步,同时停掉slave的I/O线程,
主库:
session1:插入一条数据hang住
session2:插入一条数据hang住
session3:插入一条数据hang住
其中session1状态为等待ACK,其他session状态为query end

问为什么其他session状态不是等待ACK而是query end。如果是after_commit模式则全部是等待ACK状态

实际上拿到这位朋友的pstack后大概就能确认大概是什么问题如下:

  • 等待ACK线程:
Thread 7 (Thread 0x7f44607aa700 (LWP 24897)):
#0  0x00007f4475b02a5e in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1  0x00007f44603e35d3 in ReplSemiSyncMaster::commitTrx(char const*, unsigned long long) () from /usr/local/mysql/lib/plugin/semisync_master.so
#2  0x0000000000c8197a in Binlog_storage_delegate::after_sync(THD*, char const*, unsigned long long) ()
#3  0x0000000000edd46b in call_after_sync_hook(THD*) ()
#4  0x0000000000eed935 in MYSQL_BIN_LOG::ordered_commit(THD*, bool, bool) ()
#5  0x0000000000eedf55 in MYSQL_BIN_LOG::commit(THD*, bool) ()
#6  0x000000000081e494 in ha_commit_trans(THD*, bool, bool) ()
#7  0x0000000000dce032 in trans_commit_stmt(THD*) ()
#8  0x0000000000d134e7 in mysql_execute_command(THD*, bool) ()
  • 等待LOCK_commit mutex线程
Thread 6 (Thread 0x7f4460769700 (LWP 25017)):
#0  0x00007f4475b05334 in __lll_lock_wait () from /lib64/libpthread.so.0
#1  0x00007f4475b0060e in _L_lock_995 () from /lib64/libpthread.so.0
#2  0x00007f4475b00576 in pthread_mutex_lock () from /lib64/libpthread.so.0
#3  0x0000000000eed31f in MYSQL_BIN_LOG::change_stage(THD*, Stage_manager::StageID, THD*, st_mysql_mutex*, st_mysql_mutex*) ()
#4  0x0000000000eed5e8 in MYSQL_BIN_LOG::ordered_commit(THD*, bool, bool) ()
#5  0x0000000000eedf55 in MYSQL_BIN_LOG::commit(THD*, bool) ()
#6  0x000000000081e494 in ha_commit_trans(THD*, bool, bool) ()
#7  0x0000000000dce032 in trans_commit_stmt(THD*) ()

这里就很明显其他提交事物(这里指的是 Thread 6)堵塞在了MYSQL_BIN_LOG::change_stage函数上,其作用正是获取某个阶段的Mutex。而本线程(这里指的是 Thread 7)则是在ReplSemiSyncMaster::commitTrx上堵塞在某个Mutex上。

五、after_commit和after_sync的代码位置和区别

这里直接用代码说明进行给出,当然我只是提取了说明问题的代码片段:

  • commit阶段:
1、 change_stage(thd, Stage_manager::COMMIT_STAGE,final_queue, leave_mutex_before_commit_stage,&LOCK_commit))//持有LOCK_commit mutext进入commit阶段
2、 sync_error= call_after_sync_hook(commit_queue);//这里调用after sync hook 其在LOCK_commit保护下 此时还没有做引擎层commit
3、 process_commit_stage_queue(thd, commit_queue);//进行引擎层提交操作,具体细节以后在研究
4、 mysql_mutex_unlock(&LOCK_commit);//这里提交完成解锁队列
5、 stage_manager.signal_done(final_queue); //这里唤醒全部本组堵塞在 flush阶段的follower线程 分别做提交 但是如果是order commit 提交已经做完 这里什么都不需要做了
6、 (void) finish_commit(thd); //finish_commit会调用 atfer commit hook 其不在LOCK_commit保护下

如果抛开代码总结如下:

  • 1、leader 持有LOCK_commit 锁 进入 commit阶段。
  • 2、如果是设置after_sync,使用after sync 挂钩来确认ack 。
  • 3、进行引擎层提交,完成后解锁LOCK_commit 锁。
  • 4、唤醒所有 follwer线程。
  • 5、如果设置是after_commit,使用after commit 挂钩来确认ack 。

这里我们可以清楚的看到,他们的区别,实际上正如其名字一样就是说到底在那个步骤进行日志传输完成的确认,是在实际引擎层提交之前还是之后,如果是在之前则在mutex LOCK_commit的保护下,如果是在之后则不需要持有LOCK_commit mutex,这也是为什么会出现上面那个堵塞案例的原因。在5.7中默认是after_sync设置为after_sync后显然更加安全,如果是after_commit极端情况下可能引擎层已经提交完成,事物对主库可见,但是从库还没有传输完成如果从库奔溃可能出现少事物的情况。


结语

对于5.7中安全的设置应该尽量保证sync_binlog=1同时设置rpl_semi_sync_master_wait_point为after_sync,这实际上都是默认设置。

作者微信:
image.png

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
25天前
|
安全 关系型数据库 MySQL
如何将数据从MySQL同步到其他系统
【10月更文挑战第17天】如何将数据从MySQL同步到其他系统
145 0
|
1月前
|
存储 关系型数据库 MySQL
一个项目用5款数据库?MySQL、PostgreSQL、ClickHouse、MongoDB区别,适用场景
一个项目用5款数据库?MySQL、PostgreSQL、ClickHouse、MongoDB——特点、性能、扩展性、安全性、适用场景比较
|
10天前
|
存储 SQL 关系型数据库
mysql 的ReLog和BinLog区别
MySQL中的重做日志(Redo Log)和二进制日志(Binary Log)是两种重要的日志系统。重做日志主要用于保证事务的持久性和原子性,通过记录数据页的物理修改信息来恢复未提交的事务更改。二进制日志则记录了数据库的所有逻辑变化操作,用于数据的复制、恢复和审计。两者在写入时机、存储方式、配置参数和使用范围上有所不同,共同确保了数据库的稳定性和可靠性。
|
14天前
|
缓存 监控 关系型数据库
如何根据监控结果调整 MySQL 数据库的参数以提高性能?
【10月更文挑战第28天】根据MySQL数据库的监控结果来调整参数以提高性能,需要综合考虑多个方面的因素
54 1
|
1月前
|
SQL 存储 关系型数据库
Mysql主从同步 清理二进制日志的技巧
Mysql主从同步 清理二进制日志的技巧
27 1
|
1月前
|
存储 SQL 关系型数据库
mysql中主键索引和联合索引的原理与区别
本文详细介绍了MySQL中的主键索引和联合索引原理及其区别。主键索引按主键值排序,叶节点仅存储数据区,而索引页则存储索引和指向数据域的指针。联合索引由多个字段组成,遵循最左前缀原则,可提高查询效率。文章还探讨了索引扫描原理、索引失效情况及设计原则,并对比了InnoDB与MyISAM存储引擎中聚簇索引和非聚簇索引的特点。对于优化MySQL性能具有参考价值。
|
2月前
|
消息中间件 canal 关系型数据库
Maxwell:binlog 解析器,轻松同步 MySQL 数据
Maxwell:binlog 解析器,轻松同步 MySQL 数据
301 11
|
1月前
|
SQL 关系型数据库 MySQL
数据库:MYSQL参数max_allowed_packet 介绍
数据库:MYSQL参数max_allowed_packet 介绍
59 2
|
25天前
|
存储 关系型数据库 MySQL
MySQL中的Redo Log、Undo Log和Binlog:深入解析
【10月更文挑战第21天】在数据库管理系统中,日志是保障数据一致性和完整性的关键机制。MySQL作为一种广泛使用的关系型数据库管理系统,提供了多种日志类型来满足不同的需求。本文将详细介绍MySQL中的Redo Log、Undo Log和Binlog,从背景、业务场景、功能、底层实现原理、使用措施等方面进行详细分析,并通过Java代码示例展示如何与这些日志进行交互。
52 0
|
1月前
|
存储 JSON 关系型数据库
MySQL 5.x和MySQL 8.x到底有什么区别?
本文详细对比了MySQL 5.x与MySQL 8.x的主要区别,包括存储引擎改进、性能提升、SQL语法增强(如窗口函数、CTE、JSON支持)、安全性和权限管理、并发及锁机制、InnoDB引擎增强、复制与高可用性等方面的显著差异。通过具体示例展示了8.x版本在企业级应用和高并发场景下的优越表现,建议有条件时尽早升级至MySQL 8.x以充分利用其新特性。