共计 4052 个字符,预计需要花费 11 分钟才能阅读完成。
导读 | 众所周知,在 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,这个函数,核心步骤如下:
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. 另外,还有一个用处,就是 mysql 的 5.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<int,my_off_t>result= flush_thread_caches(head);
total_bytes+= result.second;
if(flush_error == 1)
flush_error= result.first;
#ifndef DBUG_OFF
no_flushes++;
#endif
}
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 的完整代码如下。
通过上面的步骤介绍,我们看到,在 binlog 文件的最新位置更新的时候,就已经通过 signal_update 函数发送信号给 binlog 的 dump 线程,该线程就可以将事务的 binlog 同步到从库,从库接收到日志之后,就可以 relay 日志,实现了主从同步。因此,再次重复说明一下,按照上面的解释,在事务真正提交完成之前就开始发送了 binlog 已经更新的信号,dump 线程收到信号,即可以进行 binlog 的同步。而 semisync 的作用是什么呢?
实际上,有没有 semisync 机制,上面介绍的 mysql 的有关事务提交中关于 binlog 的流程都是一样的,semisync 的作用,只是主从之间的一个确认过程,主库等待从库返回相关位置的 binlog 已经同步到从库的确认,(而实际实现则是等待 dump 线程给用户会话线程一个回复),没有得到确认之前(或者等待时间达到 timeout),事务提交则在该函数(步骤)上等待直至获得返回。具体执行 binlog 已经同步到某个位置的的确认函数为 repl_semi_report_binlog_sync,函数如下:
int repl_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)
return repl_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 吞吐量的关键点,当这个关键点被拉长,所以对全局的影响就被放大。虽然仅仅多了这么一个确认的动作,但当主库处于 semisync 的同步状态与异步状态的吞吐量相比,相差了好几倍。上述解释就是其真正的原因。
![post-qrcode](https://www.xgss.net/wp-content/uploads/2023/07/二维码-微信公众号256x256.jpg)