MySQL/InnoDB和Group Commit(1)

估计相关的东西一篇文章是讲不清楚的。

这个问题由来已久,Kristian Nielsen连续写了四篇文章《Fixing MySQL group commit》(part 1 | part 2 | part 3 | part 4 )深入细致的分析了“故事”的前因后果。本文完全没有任何新意,仅做一个小的总结。这里会先介绍一下什么是“Group Commit”,MySQL/InnoDB里面的Group Commit为什么引起如此大的关注,现在是怎么解决问题的。

1. 什么是Group Commit

简单的,InnoDB在每次提交事务时,为了保证数据已经持久化到磁盘(Durable),需要调用一次fsync(或者是fdatasync、或者使用O_DIRECT选项等)来告知文件系统将可能在缓存中的数据刷新到磁盘(更多关于fsync)。而fsync操作本身是非常“昂贵”的(关于“昂贵”:消耗较多的IO资源,响应较慢):传统硬盘(10K转/分钟)大约每秒支撑150个fsync操作,SSD(Intel X25-M)大约每秒支撑1200个fsync操作。所以,如果每次事务提交都单独做fsync操作,那么这里将是系统TPS的一个瓶颈。所以就有了Group Commit的概念。

当多个事务并发时,我们让多个都在等待fsync的事务一起合并为仅调用一次fsync操作。这样的一个简单的优化将大大提高系统的吞吐量,Yoshinori Matsunobu的实验表明,这将带来五到六倍的性能提升。

2. MySQL/InnoDB与Group Commit

MySQL5.0开始,InnoDB就不再支持Group Commit了。在2005年,Peter Zaitsev就将此作为一个Bug提交,直到2009年在InnoDB Plugin1.0.4才将此问题修复。

2.1 为什么5.0之后MySQL/InnoDB无法支持Group Commit

究其更本原因发现,5.0之后MySQL开始支持“分布式事务和两阶段提交协议”(distributed transactions and Two Phase Commit /2PC),在代码实现时为了保证Binlog中的事务顺序和InnoDB Log(Transaction Log或者叫redo log)事务顺序一致,被动放弃了Group Commit。如果Binlog顺序不一致,那么备库就无法确保和主库有一致的数据。参考上面的Part 2给出代码相关的解释:

InnoDB本身的代码实现是可以支持Group Commit的,我们来看下面的代码:

第一阶段:在内存中注册事务commit(还没有持久化,这是比较快的)
trx->flush_log_later = TRUE;
innobase_commit_low(trx);
trx->flush_log_later = FALSE;
第二阶段:调用write和fsync
trx_commit_complete_for_mysql(trx)

上面的第二个阶段数据持久化是比较慢的,所以当多线程并发完成第一阶段后,可以一起只调用一次fsync来持久化全部数据(fsync应该是以文件描述符为单位的)。

如果打开了二进制日志,那么代码就有些不一样了。MySQL使用了“两阶段提交”(XA/2-phase commit )来保证存储引擎和二进制日志的一致,在上面的过程之前有多了一步:

innobase_xa_prepare()
write() and fsync() binary log
innobase_commit()

在innobase_xa_prepare函数中需要获取排他锁prepare_commit_mutex,直到上面的innodb_commit结束锁才释放,所以上面的binlog write and fsync也就无法Group操作了(当binlog_sync=1时)。当设置Binlog_sync=1时,InnoDB就无法做Group_commit了,如果prepare_commit_mutex提前释放了,则可能导致InnoDB内部的事务顺序和Binlog内部事务的顺序不一致(这里值得商榷,参考下面的参考文献6)。

2.2 Facebook的解决方案

Facebook作为一个SNS(Social network service)网站,在互联网界已经创造了奇迹。在技术领域Facebook也为开源社区(包括MySQL PHP Linux等)做了很多的杰出的贡献,希望我们公司能以此为目标。

开源社区为此也展开了很多讨论,Mark Callaghan(Facebook)提出的”Ticket System“目前看到的最简洁而有效的实现。

Facebook尝试两种方式实现,一,通过一个InnoDB的UT_LIST链表来确保InnoDB和Binlog内部的顺序一致;二,实现一个简单的“Ticket System”,当多个线程都进入innobase_xa_prepare()阶段时,就分配一个”ticket”值,并且分配的ticket值是单调增加的。当线程的binlog准备commit时,必须保证小于当前ticket的binlog都提交了,如果这时有多个事务的binlog则可以同时合并提交。

这里可以看到相关的性能测试情况:Laying the foundatation for group commit

2.2 InnoDB Plugin

在InnoDB Plugin 1.0.4声称解决了这个问题。下面是Plugin手册上的一段原话:

Beginning with InnoDB Plugin 1.0.4, the group commit functionality inside InnoDB works with the Two Phase Commit protocol in MySQL. Re-enabling of the group commit functionality fully ensures that the ordering of commit in the MySQL binlog and the InnoDB logfile is the same as it was before.

相关的性能测试数据。(sync_binlog = 0时)

很遗憾,我还不知道具体的实现细节,也还没有去检查源代码看看有没有相关实现说明。

Update 2011-07-25: InnoDB Plugin关于Group commit代码在storage/innobase/trx/trx0trx.c中实现,可以尝试阅读。

Update(2011-11-06):当sync_binlog = 0,MySQL并不能Group commit。

Update(2011-12-20):当打开二进制日志时,MySQL如何提交事务(这是一个两阶段提交):

binlog_prepare (do nothing)
innodb_prepare

binlog_commit
innobase_commit

参考文献

1. Binary Log Group Commit – An Implementation Proposal

2. Ease of Switching to the InnoDB Plugin and the Numerous Benefits

3. Eventually consistent Group Commit

4. Innodb plugin 1.0.4 released – great job Innobase

5. The followings are just my current understanding about broken group commit

6. Fixing MySQL group commit part 1 | part 2 | part 3 | part 4

In:

4 responses to “MySQL/InnoDB和Group Commit(1)”

  1. […] 这篇文章接前篇继续介绍一下问题的背景:什么是Group Commit,现在的官方版本Group Commit做到了什么程度? 1. 什么是Group Commit […]

  2. […] group commit的分析,可参考系列文章:Mysql/InnoDB和Group Commit(1);Mysql/InnoDB和Group […]

  3. […] group commit的分析,可参考系列文章:MySQL/InnoDB和Group Commit(1);MySQLl/InnoDB和Group […]

Leave a Reply

Your email address will not be published. Required fields are marked *