1 为什么会有组提交
关系数据库要保证事务的永久性,通常采用Write-Ahead Log (WAL)机制,在对数据库的更改应到实际数据库之前(写到数据库的数据文件离去) 必须先将更改写入一个日志文件中去。MySQL也不例外,在事务提交之前必须将事务对数据的更改先写到日志文件中。早期的MySQL数据库在每个事务提交之前,总是先写日志文件,如下面的伪代码
/* pseudo code */
LOCK(log_mutex);
write_data();
fsync();
unlock(log_mutex);
在写日志前,先获得相应的互斥锁,然后将数据写入磁盘缓冲区,调用fsync将数据同步到磁盘。每一个事务的提交都要调用一次上面的过程。早期服务器所采用的机械磁盘每秒可以完成100多次,不到200多次的IO操作,采用这种机制的数据库很快就达到了IO瓶颈。
在组提交和固态盘之前,有两种技术可以获得更多的并发事务数。
一是采用RAID技术,通过多个磁盘提供更告的IOPS,这个RAID技术一般是采用磁盘阵列技术来实现的。为了进一步提高IOPS指标,磁盘阵列上通常还会配置比较大的高速缓存(内存),大部分服务器的RAID卡上也有高速缓存,还要配置一块电池,以防磁盘阵列突然重启导致数据丢失。以前做运维的时候,遇到过几次数据库性能急剧下降的情况,原因是磁盘阵列上的电池耗尽了,磁盘阵列关闭了高速缓存,导致IO性能大幅度下降。
二是采用分片技术,将数据按照一定的规则分布到几十台或者上千台服务器上,有前端的数据库中间件统一调度对数据库的读写,早期的数据库中间件如mycat等就是实现这个功能的,现在数据库分片技术已经发展到了分布式数据库,如阿里的polardb-X。
组提交是另一个解决方案,着眼于事务提交过程的优化,通过将多个事务的日志组成一个组一起写磁盘来优化IO,提升IO性能。
要理解组提交,首先要了解一下MySQL数据库的事务提交过程。
2 MySQL数据库的事务提交过程

MySQL的提交分为显示提交( COMMIT, autocommit)和隐式提交(DDL命令),不管是显式提交还是隐式提交,MySQL都采用两阶段提交协议(2PC),一次调用 COMMIT, autocommit两个阶段的代码路径。下面两个点容易被忽略
- Binary log code实现两阶段提交协议的事务协调器,实现了一个存储引擎接口“handlerton” 。
- 每一条语句都要提交,不只是commit语句。
MySQL提交的过程简述如下
2.1 prepare阶段
获取commit parent,作为二进制日志文件的last_committed 值
将InnoDB主备记录写入到内存日志缓冲区
注意的是,这个阶段,commit不做任何操作
2.2 commit阶段
提交阶段三个过程,flush,sync 和commit,对应的操作是将日志写入缓冲区,同步到磁盘,事务提交相关操作,简述如下
2.2.1 flush
如图所示,这个阶段又有两个小的阶段,flush queue和flush 处理阶段。
flush queue阶段,线程进入flush队列,检查队列是否为空,如果不为空,线程成为跟随者,转到第6步,等待pthread_cond_wait值设定的时间;如果队列为空,线程进入队列成为队列的leader,并继续。
flush处理阶段,取得flush队列里的所有线程,这一步紧接着上一步执行,刷新Innodb准备记录,为每一事务指定一个逻辑时钟值(每一个事务的逻辑时钟值都在逻辑时钟的当前值前进一步),这个值是事务在二进制文件里的sequence_number值,写事日志(不做flush操作)二进制日志。这里要注意的是,每一个日志都有它的<last_committed(来自prepare阶段, sequence_number(刚刚获取的)>值。
2.2.2 sync阶段
线程进入同步队列,检查队列是否为空。 如果队列不为空,线程成为追随者,转到第6步,等待pthread_cond_wait值设定的时间,如果为空,线程成为领先者,继续执行。
等待 binlog_group_commit_sync_no_delay_count值设置得事务数或者是binlog_group_commit_sync_delay设置得时间,这里得等待就是
就是上面提到的两个系统变量可以影响组提交批次大小之处: 等待越长,进入同步队列等待执行下一步操作的线程越多。
取得同步队列里的所有线程,刷新二进制日志(同步过程)。
这就是一个二进制日志组提交操作:这里的组是同步队列里所有线程执行的事务。
2.2.3 commit
线程进入提交队列,检查队列是否为空。
如果不为空,线程成为追随者,转到第6步,等待pthread_cond_wait值设定的时间;如果为空,线程成为leader,继续执行
如果leader的值更大,更新全局最大提交值,写并同步Innodb提交记录,将最终改变落到数据库中并释放锁。
唤醒追随者-提交阶段的leader调用 pthread_cond_broadcast,追随者醒来,如果追随者的序列号更大,更新全局最大提交值。
执行这一步的目的是确保全局最大提交值得正确。
3 MySQL 事务提交过程分析
这三个阶段中,flush阶段的队列和处理阶段之间没有等待,进入队列的线程立即进行处理,flush阶段的处理包括两个磁盘操作,一个是将prepare记录写道Innodb的REDO日志文件中去,这是一个单线程的顺序磁盘操作,只有一个io操作,不会有瓶颈,另一个操作是将事务的事件(多个事件)写入到数据库的二进制日志文件中,这一步只进行写操作,不落盘,涉及到对操作系统的文件缓冲区的操作,不涉及磁盘IO操作,也非常快。
第二个阶段是sync阶段,这个阶段的队列和处理之之间有一个等待,在图中是一个时钟,等待的时间由binlog_group_commit_sync_no_delay_count 和 binlog_group_commit_sync_delay参数决定。这是binlog组提交设计的关键理念,在sync队列后设置一个等待时间,在这个时间段内,线程会不断进入等待队列。到达等待时间后,由leader线程将事件刷新到二进制文件内(队列内的线程已将事务事件写入文件缓冲区)。
第三个阶段是commit阶段,在这个阶段,队列内的leader线程更新global max committed value值,然后唤醒跟随者线程,跟随者线程的提交值更大,则更新global max committed value值。这一步不涉及磁盘操作,非常快。
4 影响组提交的参数
影响组提交的参数由两个: binlog_group_commit_sync_delay和binlog_group_commit_sync_no_delay_count,前者指定延迟的时间,单位是微秒,后者指定提交前事务的个数。
默认情况下,这两个参数的值都是0,这种情况下并不意味着组提交的关闭,MySQL的组提交总是生效的,在有可能的情况下MySQL总是会使用组提交技术,比如下面两种情况
- 线程同时到达队列
- leader线程因为某些原因阻塞
当这两个参数任意一个参数的值为非零值时,更容易发生组提交。调整这两个值之前,要慎重考虑,这会导致事务提交普遍变慢。当binlog_group_commit_sync_delay值为零时,设置binlog_group_commit_sync_no_delay_count没有任何效果,当binlog_group_commit_sync_delay为非零值时,当进入队列的事务数达到binlog_group_commit_sync_no_delay_count值时,将退出等待,进行同步操作。
5 写在后面
binlog 组提交只是MySQL 组提交的一部分,Innodb也有自己的组提交技术。调整组提交相关参数可能会提高数据库的并发支持能力,但是会提高每个事务的提交时间。在并发达到一定程度或者是负载比较重的时候,组提交会自动生效,因此,一般情况下不建议调整组提交的两个参数,如需要调整,不仅需要验证对业务的影响,也需要验证对使用binlog复制的备库的影响。
虽然组提交的相关参数不需要调整,但理解了组提交技术对分析高并发下的数据库性能很有帮助。




