mysql复制那点事(2)-binlog组提交源码分析和实现
mysql复制那点事(2)-binlog组提交源码分析和实现
0. 参考文献
| 序号 | 文献 | 
|---|---|
| 1 | MySQL 5.7 MTS源码分析 | 
| 2 | MySQL 组提交 | 
| 3 | MySQL Redo/Binlog Group Commit , 2pc事务两阶段提交,Crash Recovery浅析 | 
| 4 | MySQL · 物理备份 · Percona XtraBackup 备份原理 | 
| 5 | 条件变量(Condition Variable)详解 | 
| 6 | Linux线程同步之条件变量 | 
本文主要介绍了mysql binlog组提交的原理和源码实现。感谢上述参考文献在本文形成的过程中提供的帮助。本文所介绍的内容如下:
- mysql 两阶段提交实现的历史以及存在的问题
 - mysql binlog 组提交实现的原理
 
1. innodb和binlog的两阶段提交
众所周知,事务在innodb上提交的时候需要日志先行WAL(Write-Ahead-Log)。在binlog开启的情况下,为了保证binlog和存储引擎的一致性,会在事物提交的时候自动开启两阶段提交。对于单个事务,mysql实现的两阶段提交流程如图所示(参考文献 1 和 文献2 ):
- 当事务进入PrePare阶段的时候,会在存储引擎层进行提交。生成undo log 和redo log 内存日志。
 - 之后生成binlog并调用sync落盘。
 - 在存储引擎层提交,通过 innodb_flush_log_at_trx_commit 参数的设置,使 undo 和 redo 永久写入磁盘。
 
在mysql启动恢复的阶段,会执行如下的操作:
- 如果事务在prepare 阶段mysql异常退出,且binlog和innodb都没有提交。则在恢复阶段直接忽略这个事务不进行提交。
 - 如果事务在innodb commit的阶段异常,但是binlog已经写入了磁盘。则在恢复的时候,mysql会从binlog中提取信息,并把这个事务重做。
 
以上是mysql在开启binlog的情况下使用两阶段提交保证binlog和innodb层面都提交的流程。不过在并发的情况下,会存在一定的问题。如图所示,有3个事务T1,T2,T3 进入Prepare阶段:
下面来说明下图中T1,T2,T3提交的过程中都发生了什么:
- T1 ,T2,T3依次写入binlog文件,并调用fsync一次性写入磁盘。
 - T2,T3 先行进入提交阶段执行commit。
 - 在T1提交之前,做了一次热备份(例如使用mysqlbackup,xtrabackup等工具)。此时因为T1没有提交,备份工具记录的当前binlog位置是指向的T3提交的时刻。
 - T1提交。
 
如果此时DBA使用上面第三点的备份数据,在其他机器上恢复备份并搭建主从复制,则T1事务会完美的被错过造成主从数据不一致。原因是因为备份开始同步binlog的位置是指向了T3提交的时刻(不会拉取T3提交时刻以前的binlog,因此T1提交的binlog不会被读取),而且因为T1在备份时刻没有提交,则在恢复备份的时候会被mysql回滚。
对于这个问题,在mysql5.6之前使用 prepare_commit_mutex 保证顺序。并且只有当上一个事务 commit 后释放锁,下个事务才可以进行 prepara 操作,并且在每个事务过程中 binlog 没有 fsync() 的调用。接下来介绍下,在使用prepare_commit_mutex 保证事务顺序提交的时候,为什么能够解决这个问题。
同样如上图所示,展示了3个事务T1,T2,T3顺序提交的时候的过程。如果DBA在T3写入binlog之后commit之前建立了一次备份,则如上所述T3 因为没有提交,在恢复备份的时候会被回滚。之后DBA在搭建同步的时候,根据备份时候备份工具(例如使用mysqlbackup,xtrabackup等工具)记录的参数从T2commit的时刻开始拉取binlog,则此时可以拉取到T3提交的事务并重放,因此保证了主从的一致性。在这里,可以看出如果使用了prepare_commit_mutex保证顺序提交,则会极大的影响mysql的并发性能。因此在mysql5.6开始提出了binlog组提交的改进。
2. 组提交原理
上文提到mysql5.6 之后对于binlog的提交做了改进。首先去掉了prepare_commit_mutex锁,并且把整个commit阶段分为3个部分:
- FLUSH:在这个阶段leader事务把thd的缓存写到binlog文件的缓存中。
 - SYNC:在这个阶段leader事务调用fsync把缓存一次性落盘。
 - COMMIT :在这个阶段,根据参数binlog_order_commits的设定,让事务依次提交或者各种提交(binlog中提交的顺序可能会和innodb中提交的顺序不同)
 
组提交的流程如图所示:
从上图中可以看出,每个阶段都会产生一个leader进程。当一个事务进程进入队列的时候,会有如下的2种情况:
- 队列为空。
 - 队列中已有其他的事务。
 
在第一种情况下,当前事务称为leader进程,后续进来事务成为follower 并使用条件变量进入休眠。后续的工作会由leader进程代替follower进程完成。在第二种情况下,当前事务会成为followr进而休眠等到leader 完成剩余的工作。
3. 组提交实现
前文介绍了组提交的原理,本小节将介绍下组提交在mysql源码层面上的实现过程。本文去掉了代码中关于错误处理、同步和其他输出代码,保留了组提交主流程的相关代码。
3.1 order_commit
如上图所示,组提交的入口是order_commit 函数:
 9498 int MYSQL_BIN_LOG::ordered_commit(THD *thd, bool all, bool skip_commit)
 9499 {
  				... ...
 9570   if (change_stage(thd, Stage_manager::FLUSH_STAGE, thd, NULL, &LOCK_log))
 9571   {
 9572     DBUG_PRINT("return", ("Thread ID: %u, commit_error: %d",
 9573                           thd->thread_id(), thd->commit_error));
 9574     DBUG_RETURN(finish_commit(thd));
 9575   }
          ... ...
 9594   flush_error= process_flush_stage_queue(&total_bytes, &do_rotate,
 9595                                                  &wait_queue);
          ... ...
 9646   /*
 9647     Shall introduce a delay only if it is going to do sync
 9648     in this ongoing SYNC stage. The "+1" used below in the
 9649     if condition is to count the ongoing sync stage.
 9650     When sync_binlog=0 (where we never do sync in BGC group),
 9651     it is considered as a special case and delay will be executed
 9652     for every group just like how it is done when sync_binlog= 1.
 9653   */
 9654   if (!flush_error && (sync_counter + 1 >= get_sync_period()))
 9655     stage_manager.wait_count_or_timeout(opt_binlog_group_commit_sync_no_delay_count,
 9656                                         opt_binlog_group_commit_sync_delay,
 9657                                         Stage_manager::SYNC_STAGE);
   				... ...
 9639   if (change_stage(thd, Stage_manager::SYNC_STAGE, wait_queue, &LOCK_log, &LOCK_sync))
 9640   {
 9641     DBUG_PRINT("return", ("Thread ID: %u, commit_error: %d",
 9642                           thd->thread_id(), thd->commit_error));
 9643     DBUG_RETURN(finish_commit(thd));
 9644   }
          ... ...
 9661   if (flush_error == 0 && total_bytes > 0)
 9662   {
 9663     DEBUG_SYNC(thd, "before_sync_binlog_file");
 9664     std::pair<bool, bool> result= sync_binlog_file(false);
 9665     sync_error= result.first;
 9666   }
 9667
 				 ... ...
 9702 commit_stage:
 9703   if (opt_binlog_order_commits &&
 9704       (sync_error == 0 || binlog_error_action != ABORT_SERVER))
 9705   {
 9706     if (change_stage(thd, Stage_manager::COMMIT_STAGE,
 9707                      final_queue, leave_mutex_before_commit_stage,
 9708                      &LOCK_commit))
         ... ...
 9736     process_commit_stage_queue(thd, commit_queue);
 9737     mysql_mutex_unlock(&LOCK_commit);
         ... ...
 9759   /* Commit done so signal all waiting threads */
 9760   stage_manager.signal_done(final_queue);
         ... ...
       }
如源码所示,在commit阶段会调用change_stage函数3次,分别传入不同的参数FLUSH_STAGE、SYNC_STAGE和COMMIT_STAGE。change_stage主要用于事务加入队列。在代码中有一个值得注意的地方是在9655行中,sync落盘缓存之前会等到binlog_group_commit_sync_delay毫秒或收集到binlog_group_commit_sync_no_delay_count个事务之后再sync。
3.2 change_stage和enroll_for
change_stage 函数主要的作用是将当期事务加入对应的队列中,并返回这个事务是否成为leader。函数关键代码如下所示:
 9140 bool
 9141 MYSQL_BIN_LOG::change_stage(THD *thd,
 9142                             Stage_manager::StageID stage, THD *queue,
 9143                             mysql_mutex_t *leave_mutex,
 9144                             mysql_mutex_t *enter_mutex)
 9145 {
        ... ...
 9156   if (!stage_manager.enroll_for(stage, queue, leave_mutex))
 9157   {
 9158     DBUG_ASSERT(!thd_get_cache_mngr(thd)->dbug_any_finalized());
 9159     DBUG_RETURN(true);
 9160   }
        ... ...
      }
在change_stage函数中主要调用了enroll_for函数进行注册,enroll_for函数关键代码如下:
 2149 bool
 2150 Stage_manager::enroll_for(StageID stage, THD *thd, mysql_mutex_t *stage_mutex)
 2151 {
 2152   // If the queue was empty: we're the leader for this batch
 2153   DBUG_PRINT("debug", ("Enqueue 0x%llx to queue for stage %d",
 2154                        (ulonglong) thd, stage));
 2155   bool leader= m_queue[stage].append(thd);
 2156
   			... ...
 2213   if (!leader)
 2214   {
 2215     mysql_mutex_lock(&m_lock_done);
 2216 #ifndef DBUG_OFF
 2217     /*
 2218       Leader can be awaiting all-clear to preempt follower's execution.
 2219       With setting the status the follower ensures it won't execute anything
 2220       including thread-specific code.
 2221     */
 2222     thd->get_transaction()->m_flags.ready_preempt= 1;
 2223     if (leader_await_preempt_status)
 2224       mysql_cond_signal(&m_cond_preempt);
 2225 #endif
 2226     while (thd->get_transaction()->m_flags.pending)
 2227       mysql_cond_wait(&m_cond_done, &m_lock_done);
 2228     mysql_mutex_unlock(&m_lock_done);
 2229   }
 2230   return leader;
 2231 }
在代码中可以看出在接入对应的队列后,如果发现当前事务不能成为leader 则会在后续调用条件变量进行休眠。当order_commit函数中,leader 完成了所有的任务,则在9760行使用条件变量唤醒其他Follower进程。follower进程会调用DBUG_RETURN(finish_commit(thd))完成commit并退出函数。
4. 小结
本文主要介绍了关于binlog组提交的逻辑。限于本文的作者水平有限,文中的错误在所难免,恳请大家批评指正。
mysql复制那点事(2)-binlog组提交源码分析和实现的更多相关文章
- (3.10)mysql基础深入——mysqld 服务器与客户端连接过程 源码分析【待写】
		
(3.10)mysql基础深入——mysqld 服务器与客户端连接过程 源码分析[待写]
 - 使用canal分析binlog(二) canal源码分析
		
在能够跑通example后有几个疑问 1. canal的server端对于已经读取的binlog,client已经ack的position,是否持久化,保存在哪里 2. 即使不启动zookeeper, ...
 - MySQL binlog 组提交与 XA(两阶段提交)
		
1. XA-2PC (two phase commit, 两阶段提交 ) XA是由X/Open组织提出的分布式事务的规范(X代表transaction; A代表accordant?).XA规范主要定义 ...
 - MySQL binlog 组提交与 XA(分布式事务、两阶段提交)【转】
		
概念: XA(分布式事务)规范主要定义了(全局)事务管理器(TM: Transaction Manager)和(局部)资源管理器(RM: Resource Manager)之间的接口.XA为了实现分布 ...
 - MySQL binlog 组提交与 XA(两阶段提交)--1
		
参考了网上几篇比较靠谱的文章 http://www.linuxidc.com/Linux/2015-11/124942.htm http://blog.csdn.net/woqutechteam/ar ...
 - mysql复制那点事 - Seconds_behind_Master参数调查笔记
		
目录 mysql复制那点事 - Seconds_behind_Master参数调查笔记 0. 参考文献 1. 问题背景 2. 调查结论 3. 调查与分析过程 3.1 轮转binlog时的运行逻辑 3. ...
 - mysql 5.6 binlog组提交
		
mysql 5.6 binlog组提交实现原理 http://blog.itpub.net/15480802/viewspace-1411356 Redo组提交 Redo提交流程大致如下 lock l ...
 - mysql 5.6 binlog组提交实现原理(转载)
		
http://blog.itpub.net/15480802/viewspace-1411356/ Redo组提交 Redo提交流程大致如下 lock log->mutex write redo ...
 - 【源码分析】Canal之Binlog的寻找过程
		
binlog的寻找过程可能的场景如下: instance第一次启动 发生数据库主备切换 canal server HA情况下的切换 所以这个过程是能够保证binlog不丢失的关键点. 本文从源码的角度 ...
 
随机推荐
- MQ初窥门径【面试必看的Kafka和RocketMQ存储区别】
			
MQ初窥门径 全称(message queue)消息队列,一个用于接收消息.存储消息并转发消息的中间件 应用场景 用于解决的场景,总之是能接收消息并转发消息 用于异步处理,比如A服务做了什么事情,异步 ...
 - scrapy基础知识之制作 Scrapy 爬虫 一共需要4步:
			
1.新建项目 (scrapy startproject xxx):新建一个新的爬虫项目 2.明确目标 (编写items.py):明确你想要抓取的目标 3.制作爬虫 (spiders/xxspider. ...
 - 使用java的MultipartFile实现layui官网文件上传实现全部示例,java文件上传
			
layui(谐音:类UI) 是一款采用自身模块规范编写的前端 UI 框架,遵循原生 HTML/CSS/JS 的书写与组织形式,门槛极低,拿来即用. layui文件上传示例地址:https://www. ...
 - C++学习书籍推荐《Inside the C++ Object Model》下载
			
百度云及其他网盘下载地址:点我 作者简介 Stanley B. Lippman is Architect with the Visual C++ development team at Microso ...
 - 腾讯云tomcat问题
			
Ubuntu启动特别慢 1.在$JAVA_HOME/jre/lib/security/java.security中,把securerandom.source=file:/dev/urandom替换成s ...
 - Web前端_微信小程序实战开发
			
微信小程序开发实战教程 一.微信小程序 它是一种混合开发的方式. 是安装在微信中的程序(一个程序最多2M空间). 1.1 注册 1 2 点击立即注册:进入下方页面 3 4 点击小程序进入表单填写页 ...
 - C++小游戏——井字棋
			
#include<cstdio> #include<windows.h> #include<ctime> int main() { srand(time(NULL) ...
 - D:苏卿念发红包
			
首先,题目中已经说得很明确了(按照常理也是). 当有mmm个包,你第kkk个抢.k>mk>mk>m的话.显然,平时会显示:来晚了一步,红包已经被领完了\text{来晚了一步,红包已经 ...
 - Java中的单例模式(Singleton Pattern in Java)
			
Introduction 对于系统中的某个类来说,只有一个实例是很重要的,比如只有一个timer和ID Producer.又比如在服务器程序中,配置信息保留在一个文件中,这些配置信息由一个单例对象统一 ...
 - Excel催化剂开源第28波-调用Google规划求解库
			
在Excel催化剂的自定义函数中,有规划求解的函数,用于在一些凑数的场景,某财务工作网友向我提出的需求,例如用于凑发票额使用. 一般开发票的场景是多次采购合在一起开具,即多个订单产生后开,同时发票一般 ...