Binlog开启为何和Group Commit不兼容?
MySQL/MariaDB使用XA/2阶段提交保证事务持久性。
提交事务的三个阶段:
- 准备阶段,事务在引擎中持久化,但仍可以回滚;
- 如果准备阶段成功,事务在binlog中持久化;
- 提交阶段,引擎提交事务,无法再回滚。
开启binlog之前,group commit的实现:
trx->flush_log_later = TRUE;
innobase_commit_low(trx);
trx->flush_log_later = FALSE;
最后所有事务通过同一个fsync()调用flush。
开启binlog之后,group commit的实现:
innobase_xa_prepare()
write() and fsync() binary log
innobase_commit()
在innobase_xa_prepare()阶段,锁住了prepare_commit_mutex,导致只要有一个事务在执行innobase_commit(),其他所有执行innobase_xa_prepare()的事务都会阻塞等待锁释放。
因此无法做到所有事务共同通过fsync()调用flush。
那么为什么innobase_xa_prepare()要拿着prepare_commit_mutex?为了严格保证事务提交的顺序。
当sync_binlog=1时,很明显上述的第二步会成为瓶颈,而且还是持有全局大锁,这也是为什么性能会急剧下降。
很快Mariadb就提出了一个Binlog Group Commit方案,即在准备写入Binlog时,维持一个队列,最早进入队列的是leader,后来的是follower,leader为搜集到的队列中的线程依次写Binlog文件, 并commit事务。Percona 的Group Commit实现也是Port自Mariadb。不过仍在使用Percona Server5.5的朋友需要注意,该Group Commit实现可能破坏掉Semisync的行为。
Oracle MySQL 在5.6版本开始也支持Binlog Group Commit,使用了和Mariadb类似的思路,但将Group Commit的过程拆分成了三个阶段:
- flush stage 将各个线程的binlog从cache写到文件中;
- sync stage 对binlog做fsync操作(如果需要的话);
- commit stage 为各个线程做引擎层的事务commit。
每个stage同时只有一个线程在操作。
如何修复group commit?
仅在为binlog刷盘时进行fsync(), 也就是让innodb_flush_log_at_trx_commit运行在级别2甚至级别0上。
为了能按上述步骤恢复到数据一致状态, 我们需要以下两个条件:
- 对于一个事务, 需要将其在binlog中的位置存到存储引擎中, 这样在崩溃恢复中, 我们才能知道应从binlog哪个位置起开始回放事务.
- 这次我们真的需要保证binlog和存储引擎的commit顺序一致! 否则崩溃恢复时, 有可能的情况是: binlog中两个事务的顺序是AB, 而存储引擎中仅有B被提交, 而A没被持久化到存储引擎中. 这样我们就没法决定如何恢复一致性了.
实现
1.
void commit_ordered(handlerton *hton, THD *thd, bool all);
在commit()之前调用,且保证在各引擎上严格的提交顺序。
- Ticket方式
使用 innodb的UT_LIST结构,启用force_binlog_order配置。