mysql binlog源码分析_mysql复制那点事(2)-binlog组提交源码分析和实现

本文深入分析了MySQL Binlog的组提交原理和实现,探讨了并发环境下两阶段提交的问题及解决方案。文章指出,MySQL 5.6以后通过去除prepare_commit_mutex并引入组提交来提高并发性能,详细解释了组提交的三个阶段,并展示了源码中的order_commit和change_stage函数如何协调事务提交顺序。此外,还讨论了在组提交中事务的领导者和跟随者角色以及如何通过条件变量控制事务执行。
摘要由CSDN通过智能技术生成

mysql复制那点事(2)-binlog组提交源码分析和实现

0. 参考文献

本文主要介绍了mysql binlog组提交的原理和源码实现。感谢上述参考文献在本文形成的过程中提供的帮助。本文所介绍的内容如下:

mysql 两阶段提交实现的历史以及存在的问题

mysql binlog 组提交实现的原理

1. innodb和binlog的两阶段提交

众所周知,事务在innodb上提交的时候需要日志先行WAL(Write-Ahead-Log)。在binlog开启的情况下,为了保证binlog和存储引擎的一致性,会在事物提交的时候自动开启两阶段提交。对于单个事务,mysql实现的两阶段提交流程如图所示(参考文献 1 和 文献2 ):

8dde8480ace30ef16747577573c64d88.png

当事务进入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阶段:

68cb354eb10b4b690de1b67cb2216521.png

下面来说明下图中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 保证事务顺序提交的时候,为什么能够解决这个问题。

b23a9462d8371ce494b5899858fc1c44.png

同样如上图所示,展示了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中提交的顺序不同)

组提交的流程如图所示:

4b10cf953535f64cc7dd4c5b85be94b6.png

从上图中可以看出,每个阶段都会产生一个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 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。

eedcab6250a0e3eed836639f1abade0e.png

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并退出函数。

3a5461e5de380e2439bb6d993cf374e8.png

4. 小结

本文主要介绍了关于binlog组提交的逻辑。限于本文的作者水平有限,文中的错误在所难免,恳请大家批评指正。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值