从源码探究MySQL5.7高吞吐事务量的背后操手

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介:
大家都知道在MySQL中,在事务真正COMMIT之前,会将事务的binlog日志写入到binlog文件中。在MySQL的5.7版本中,提供了所谓的无损复制功能,该功能的作用就是在主库的事务对其他的会话线程可见之前,就将该事务的日志同步到从库,保证了事务可以安全地无丢失地复制到从库。

 

下面我们从源码来分析MySQL的事务提交以及事务在何时将binlog复制到从库的。

 

MYSQL_BIN_LOG::ordered_commit,这个是事务在binlog阶段提交的核心函数,通过该函数,实现了事务日志写入binlog文件,以及触发dump线程将binlog发送到Slave,在最后的步骤,将事务设置为提交状态。

 

我们来分析MYSQL_BIN_LOG::ordered_commit这个函数的核心过程,该函数位于binlog.cc文件中。

 

源码分析

 

MYSQL_BIN_LOG::ordered_commit,这个函数,核心步骤如下:

 

第一步骤:flush
 

 

Stage#1: flushing transactions to binary log:

 

步骤1 :将事务的日志写入binlog文件的buffer中,函数如下:

process_flush_stage_queue(&total_bytes,&do_rotate, &wait_queue);

 

从5.6开始,MySQL引入了Group Commit的概念,这样可以避免每个事务提交都会锁定一次binlog。

 

另外,还有一个用处,就是MySQL5.7的基于logical_clock的并行复制。在一个组里面(其实是一个队列),这一组队列的头事务是相同的,因此这一组事务的last_committed(上一组的最后一个提交的事务)的事务也是同一个。我们都知道,last_committed相同的事务,是可以在从库并行relay(重演)的。

 

该函数process_flush_stage_queue的作用,就是将commit队列中的线程一个一个地取出,然后执行子函数 flush_thread_caches(head);循环的代码如下:将各自线程中的binlog cache写入到binlog中。

 

/* Flush thread caches to binary log. */

for (THD *head= first_seen ; head ; head = head->next_to_commit)

{

std::pair result= flush_thread_caches(head);

total_bytes+= result.second;

if(flush_error == 1)

flush_error= result.first;

#ifndef DBUG_OFF

no_flushes++;

#endif

}

 

第二步骤:SYNC to disk
 

 

Stage#2: Syncing binary log file to disk

 

第二步:将binlog file中cache的部分写入disk.但这个步骤参数sync_binlog起决定性的作用。

 

我们来看看源码,除了这些还有哪些细节步骤,听完源码分析之后,你应该有新的收获与理解。在执行真正的将binlog写到磁盘之前,会进行一个等待,函数如下:

 

stage_manager.wait_count_or_timeout(opt_binlog_group_commit_sync_no_delay_count,

opt_binlog_group_commit_sync_delay,

Stage_manager::SYNC_STAGE);

 

等待的时间由MySQL参数文件中的binlog_group_commit_sync_delay,binlog_group_commit_sync_no_delay_count 这两参数共同决定。第一个表示该事务组提交之前总共等待累积到多少个事务,第二个参数则表示该事务组总共等待多长时间后进行提交,任何一个条件满足则进行后续操作。

 

因为有这个等待,可以让更多事务的binlog通过一次写binlog文件磁盘来完成提交,从而获得更高的吞吐量。

 

接下来,就是执行sync_binlog_file,该函数会用到MySQL参数文件中sync_binlog参数的值,如果为0,则不进行写磁盘操作,由操作系统决定什么时候刷盘,如果为1,则强制进行写磁盘操作。

 

再接下来,执行update_binlog_end_pos函数,用来更新binlog文件的最后的位置binlog_end_pos,该binlog_end_pos是一个全局的变量。在执行更新该位置之前,先得找到最后一个提交事务的线程(因为是Group Commit,多个事务排队提交的机制)。因为已经将要提交事务的线程组成了一个链表,所以通过从头到尾找,可以找到最后一个线程。代码如下:

 

if(update_binlog_end_pos_after_sync)

{

THD*tmp_thd= final_queue;

while(tmp_thd->next_to_commit != NULL)

tmp_thd= tmp_thd->next_to_commit;

update_binlog_end_pos(tmp_thd->get_trans_pos());

}

 

接下来,我们来看一下这个函数update_binlog_end_pos。这个函数很简单,传入一个pos,然后将其赋值给全局变量binlog_end_pos,接下来就是最核心的一行代码,signal_update(),发送binlog更新的信号。因此从主库同步binlog到从库的dump线程,会接收到这个binlog已有更新的信号,然后启动dump binlog的流程。

 

函数update_binlog_end_pos的完整代码如下:

void update_binlog_end_pos(my_off_tpos)

{

lock_binlog_end_pos();

if (pos >binlog_end_pos)

binlog_end_pos= pos;

signal_update();

unlock_binlog_end_pos();

}

 

 
Semi-sync
 
 

通过上面的步骤介绍,我们可以看到在binlog文件的最新位置更新的时候,就已经通过signal_update函数发送信号给binlog的dump线程,该线程就可以将事务的binlog同步到从库,从库接收到日志之后,就可以relay日志,实现了主从同步。

 

因此,再次重复说明一下,按照上面的解释,在事务真正提交完成之前就开始发送了binlog已经更新的信号,dump线程收到信号,即可以进行binlog的同步。那Semisync的作用是什么呢?

 

实际上,有没有Semisync机制,对上面介绍的MySQL的有关事务提交中关于binlog的流程都是一样的。Semisync的作用,只是主从之间的一个确认过程,主库等待从库返回相关位置的binlog已经同步到从库的确认(而实际实现则是等待dump线程给用户会话线程一个回复),没有得到确认之前(或者等待时间达到timeout),事务提交则在该函数(步骤)上等待直至获得返回。

 

具体执行binlog已经同步到某个位置的的确认函数为repl_semi_report_binlog_sync,函数如下:

intrepl_semi_report_binlog_sync(Binlog_storage_param *param,

constchar *log_file,

my_off_t log_pos)

{

if(rpl_semi_sync_master_wait_point == WAIT_AFTER_SYNC)

returnrepl_semisync.commitTrx(log_file, log_pos);

return 0;

}

 

通过观察上述函数,我们可以看到有个rpl_semi_sync_master_wait_point变量与WAIT_AFTER_SYNC比较,如果不相等,则直接返回,直接返回则表示不需要在此时此刻确认binlog是否已经同步。而这个变量的取值来自于半同步参数semi_sync_master_wait_point的初始设置,我们可以设置为after_sync与after_commit。

 

这两个参数含义的区别是:after_sync是在将binlog sync到disk之后(具体是否真正sync由参数sync_binlog的值决定)进行日志同步确认,而after_commit是将事务完成在InnoDB里面提交之后再进行binlog的同步确认。两者确认的时间点不同,after_sync要早于after_commit。

 

接下来,我们来看repl_semisync.commitTrx 这个函数,这个函数有两个传入参数,一个是binlog文件,一个binlog文件的位移。我们来看这个函数的含义吧。算了,还是直接用源码的注释来解释吧。

 

 

上面的注释说得相当清楚,就是该commiTRX函数会等待binlog-dump返回已经同步到该位置的报告,如果还没有同步到该位置,则继续等待,直到超时返回。

 

当会话线程收到该函数的返回时,事务的提交过程继续往下走,直至在InnoDB真正提交。

 

总结

 

通过上述对MySQL的事务提交过程中的前段分析,应该可以了解Semi-sync的同步机制与异步机制的区别。

 

Semi-sync的主从同步机制与异步机制在同步的处理方式上无任何区别,唯一的区别就是Semi-sync在事务提交中段(假如设置为after_sync)或者提交后的阶段(after_commit), 有一个验证该事务涉及的binlog是否已经同步到从库。而这个同步验证,会拉长整个事务的提交时间,因为事务提交在数据库中几乎是串行(如果按Group Commit为一个单位,就算是完全地串行),这是影响MySQL吞吐量的关键点,当这个关键点被拉长,对全局的影响就被放大。虽然仅仅多了这么一个确认的动作,但主库处于Semi-sync的同步状态与异步状态的吞吐量相比,相差了好几倍。

 

上述解释就是其真正的原因。

 大家都知道在MySQL中,在事务真正COMMIT之前,会将事务的binlog日志写入到binlog文件中。在MySQL的5.7版本中,提供了所谓的无损复制功能,该功能的作用就是在主库的事务对其他的会话线程可见之前,就将该事务的日志同步到从库,保证了事务可以安全地无丢失地复制到从库。

 

下面我们从源码来分析MySQL的事务提交以及事务在何时将binlog复制到从库的。

 

MYSQL_BIN_LOG::ordered_commit,这个是事务在binlog阶段提交的核心函数,通过该函数,实现了事务日志写入binlog文件,以及触发dump线程将binlog发送到Slave,在最后的步骤,将事务设置为提交状态。

 

我们来分析MYSQL_BIN_LOG::ordered_commit这个函数的核心过程,该函数位于binlog.cc文件中。

 

源码分析

 

MYSQL_BIN_LOG::ordered_commit,这个函数,核心步骤如下:

 

第一步骤:flush
 

 

Stage#1: flushing transactions to binary log:

 

步骤1 :将事务的日志写入binlog文件的buffer中,函数如下:

process_flush_stage_queue(&total_bytes,&do_rotate, &wait_queue);

 

从5.6开始,MySQL引入了Group Commit的概念,这样可以避免每个事务提交都会锁定一次binlog。

 

另外,还有一个用处,就是MySQL5.7的基于logical_clock的并行复制。在一个组里面(其实是一个队列),这一组队列的头事务是相同的,因此这一组事务的last_committed(上一组的最后一个提交的事务)的事务也是同一个。我们都知道,last_committed相同的事务,是可以在从库并行relay(重演)的。

 

该函数process_flush_stage_queue的作用,就是将commit队列中的线程一个一个地取出,然后执行子函数 flush_thread_caches(head);循环的代码如下:将各自线程中的binlog cache写入到binlog中。

 

/* Flush thread caches to binary log. */

for (THD *head= first_seen ; head ; head = head->next_to_commit)

{

std::pair result= flush_thread_caches(head);

total_bytes+= result.second;

if(flush_error == 1)

flush_error= result.first;

#ifndef DBUG_OFF

no_flushes++;

#endif

}

 

第二步骤:SYNC to disk
 

 

Stage#2: Syncing binary log file to disk

 

第二步:将binlog file中cache的部分写入disk.但这个步骤参数sync_binlog起决定性的作用。

 

我们来看看源码,除了这些还有哪些细节步骤,听完源码分析之后,你应该有新的收获与理解。在执行真正的将binlog写到磁盘之前,会进行一个等待,函数如下:

 

stage_manager.wait_count_or_timeout(opt_binlog_group_commit_sync_no_delay_count,

opt_binlog_group_commit_sync_delay,

Stage_manager::SYNC_STAGE);

 

等待的时间由MySQL参数文件中的binlog_group_commit_sync_delay,binlog_group_commit_sync_no_delay_count 这两参数共同决定。第一个表示该事务组提交之前总共等待累积到多少个事务,第二个参数则表示该事务组总共等待多长时间后进行提交,任何一个条件满足则进行后续操作。

 

因为有这个等待,可以让更多事务的binlog通过一次写binlog文件磁盘来完成提交,从而获得更高的吞吐量。

 

接下来,就是执行sync_binlog_file,该函数会用到MySQL参数文件中sync_binlog参数的值,如果为0,则不进行写磁盘操作,由操作系统决定什么时候刷盘,如果为1,则强制进行写磁盘操作。

 

再接下来,执行update_binlog_end_pos函数,用来更新binlog文件的最后的位置binlog_end_pos,该binlog_end_pos是一个全局的变量。在执行更新该位置之前,先得找到最后一个提交事务的线程(因为是Group Commit,多个事务排队提交的机制)。因为已经将要提交事务的线程组成了一个链表,所以通过从头到尾找,可以找到最后一个线程。代码如下:

 

if(update_binlog_end_pos_after_sync)

{

THD*tmp_thd= final_queue;

while(tmp_thd->next_to_commit != NULL)

tmp_thd= tmp_thd->next_to_commit;

update_binlog_end_pos(tmp_thd->get_trans_pos());

}

 

接下来,我们来看一下这个函数update_binlog_end_pos。这个函数很简单,传入一个pos,然后将其赋值给全局变量binlog_end_pos,接下来就是最核心的一行代码,signal_update(),发送binlog更新的信号。因此从主库同步binlog到从库的dump线程,会接收到这个binlog已有更新的信号,然后启动dump binlog的流程。

 

函数update_binlog_end_pos的完整代码如下:

void update_binlog_end_pos(my_off_tpos)

{

lock_binlog_end_pos();

if (pos >binlog_end_pos)

binlog_end_pos= pos;

signal_update();

unlock_binlog_end_pos();

}

 

 
Semi-sync
 
 

通过上面的步骤介绍,我们可以看到在binlog文件的最新位置更新的时候,就已经通过signal_update函数发送信号给binlog的dump线程,该线程就可以将事务的binlog同步到从库,从库接收到日志之后,就可以relay日志,实现了主从同步。

 

因此,再次重复说明一下,按照上面的解释,在事务真正提交完成之前就开始发送了binlog已经更新的信号,dump线程收到信号,即可以进行binlog的同步。那Semisync的作用是什么呢?

 

实际上,有没有Semisync机制,对上面介绍的MySQL的有关事务提交中关于binlog的流程都是一样的。Semisync的作用,只是主从之间的一个确认过程,主库等待从库返回相关位置的binlog已经同步到从库的确认(而实际实现则是等待dump线程给用户会话线程一个回复),没有得到确认之前(或者等待时间达到timeout),事务提交则在该函数(步骤)上等待直至获得返回。

 

具体执行binlog已经同步到某个位置的的确认函数为repl_semi_report_binlog_sync,函数如下:

intrepl_semi_report_binlog_sync(Binlog_storage_param *param,

constchar *log_file,

my_off_t log_pos)

{

if(rpl_semi_sync_master_wait_point == WAIT_AFTER_SYNC)

returnrepl_semisync.commitTrx(log_file, log_pos);

return 0;

}

 

通过观察上述函数,我们可以看到有个rpl_semi_sync_master_wait_point变量与WAIT_AFTER_SYNC比较,如果不相等,则直接返回,直接返回则表示不需要在此时此刻确认binlog是否已经同步。而这个变量的取值来自于半同步参数semi_sync_master_wait_point的初始设置,我们可以设置为after_sync与after_commit。

 

这两个参数含义的区别是:after_sync是在将binlog sync到disk之后(具体是否真正sync由参数sync_binlog的值决定)进行日志同步确认,而after_commit是将事务完成在InnoDB里面提交之后再进行binlog的同步确认。两者确认的时间点不同,after_sync要早于after_commit。

 

接下来,我们来看repl_semisync.commitTrx 这个函数,这个函数有两个传入参数,一个是binlog文件,一个binlog文件的位移。我们来看这个函数的含义吧。算了,还是直接用源码的注释来解释吧。

 

 

上面的注释说得相当清楚,就是该commiTRX函数会等待binlog-dump返回已经同步到该位置的报告,如果还没有同步到该位置,则继续等待,直到超时返回。

 

当会话线程收到该函数的返回时,事务的提交过程继续往下走,直至在InnoDB真正提交。

 

总结

 

通过上述对MySQL的事务提交过程中的前段分析,应该可以了解Semi-sync的同步机制与异步机制的区别。

 

Semi-sync的主从同步机制与异步机制在同步的处理方式上无任何区别,唯一的区别就是Semi-sync在事务提交中段(假如设置为after_sync)或者提交后的阶段(after_commit), 有一个验证该事务涉及的binlog是否已经同步到从库。而这个同步验证,会拉长整个事务的提交时间,因为事务提交在数据库中几乎是串行(如果按Group Commit为一个单位,就算是完全地串行),这是影响MySQL吞吐量的关键点,当这个关键点被拉长,对全局的影响就被放大。虽然仅仅多了这么一个确认的动作,但主库处于Semi-sync的同步状态与异步状态的吞吐量相比,相差了好几倍。

 

上述解释就是其真正的原因。

 

 原文发布时间为:2017-04-16

本文来自云栖社区合作伙伴DBAplus

 

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
6月前
|
运维 监控 安全
云HIS医疗管理系统源码——技术栈【SpringBoot+Angular+MySQL+MyBatis】
云HIS系统采用主流成熟技术,软件结构简洁、代码规范易阅读,SaaS应用,全浏览器访问前后端分离,多服务协同,服务可拆分,功能易扩展;支持多样化灵活配置,提取大量公共参数,无需修改代码即可满足不同客户需求;服务组织合理,功能高内聚,服务间通信简练。
206 4
|
14天前
|
关系型数据库 MySQL Linux
在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤,并与使用 RPM 包安装进行了对比
本文介绍了在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤,并与使用 RPM 包安装进行了对比。通过具体案例,读者可以了解如何准备环境、下载源码、编译安装、配置服务及登录 MySQL。编译源码安装虽然复杂,但提供了更高的定制性和灵活性,适用于需要高度定制的场景。
49 3
|
17天前
|
关系型数据库 MySQL Linux
在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤,包括准备工作、下载源码、编译安装、配置 MySQL 服务、登录设置等。
本文介绍了在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤,包括准备工作、下载源码、编译安装、配置 MySQL 服务、登录设置等。同时,文章还对比了编译源码安装与使用 RPM 包安装的优缺点,帮助读者根据需求选择最合适的方法。通过具体案例,展示了编译源码安装的灵活性和定制性。
59 2
|
1月前
|
关系型数据库 MySQL Linux
在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤
本文介绍了在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤,包括准备工作、下载源码、编译安装、配置服务等,并与使用 RPM 包安装进行了对比,帮助读者根据需求选择合适的方法。编译源码安装虽然复杂,但提供了更高的定制性和灵活性。
222 2
|
1月前
|
关系型数据库 MySQL Linux
在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤
【10月更文挑战第7天】本文介绍了在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤,包括准备工作、下载源码、编译安装、配置 MySQL 服务、登录设置等。同时,文章还对比了编译源码安装与使用 RPM 包安装的优缺点,帮助读者根据自身需求选择合适的方法。
58 3
|
5月前
|
存储 安全 Java
基于Java+MySQL停车场车位管理系统详细设计和实现(源码+LW+调试文档+讲解等)
基于Java+MySQL停车场车位管理系统详细设计和实现(源码+LW+调试文档+讲解等)
|
1月前
|
前端开发 Java 数据库连接
表白墙/留言墙 —— 中级SpringBoot项目,MyBatis技术栈MySQL数据库开发,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
本文是一份全面的表白墙/留言墙项目教程,使用SpringBoot + MyBatis技术栈和MySQL数据库开发,涵盖了项目前后端开发、数据库配置、代码实现和运行的详细步骤。
42 0
表白墙/留言墙 —— 中级SpringBoot项目,MyBatis技术栈MySQL数据库开发,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
|
3月前
|
存储 自然语言处理 关系型数据库
MySQL全文索引源码剖析之Insert语句执行过程
【8月更文挑战第17天】在MySQL中,处理含全文索引的`INSERT`语句涉及多步骤。首先进行语法解析确认语句结构无误;接着语义分析检查数据是否符合表结构及约束。随后存储引擎执行插入操作,若涉及全文索引则进行分词处理,并更新倒排索引结构。此外,事务管理确保了操作的完整性和一致性。通过示例创建含全文索引的表并插入数据,可见MySQL如何高效地处理此类操作,有助于优化数据库性能和提升全文搜索效果。
|
3月前
|
NoSQL 关系型数据库 MySQL
SpringBoot 集成 SpringSecurity + MySQL + JWT 附源码,废话不多直接盘
SpringBoot 集成 SpringSecurity + MySQL + JWT 附源码,废话不多直接盘
141 2
|
3月前
|
关系型数据库 MySQL Linux
【一键解锁神秘力量!】CentOS 7 通过编译源码方式安装 MySQL 数据库 —— 从零到英雄的数据库安装实战秘籍!
【8月更文挑战第9天】随着业务增长,对数据库的需求日益提高。在 CentOS 7 中,通过编译源码安装 MySQL 可提供更高定制性和灵活性。本文详细介绍从准备环境、下载源码、配置编译参数到安装 MySQL 的全过程,并对比 RPM 包安装方法,帮助读者根据需求选择合适方案。实践时需注意备份数据、选择合适版本、确保安全性和调优性能等要点。
217 1