mysql 5.6 binlog组提交实现原理
Redo提交流程大致如下
lock log->mutex
write redo log buffer to disk
unlock log->mutex
fsync
Fsync写磁盘耗时较长且不占用log->mutex,也就是其执行期间其他线程可以write log buffer;
假定一次fsync需要10ms,而写buffer只需要1ms,则fsync执行期间最多可以有10条redo record写入buffer,则下次调用fsync时可一次性写10条记录;
binlog组提交
Mysql 从 5.0 开始支持2PC,在代码实现时为了保证Binlog中的事务顺序和事务commit顺序一致,放弃了Group Commit。
如果Binlog顺序不一致,那么备库就无法确保和主库有一致的数据。这个问题直到 mysql 5.5 才开始部分修复,到 mysql 5.6 完全修复。
在 mysql 5.5 中,只有当 sync_binlog = 0 时,才能使用 group commit,在 mysql 5.6中都可以进行 group commit。
2PC下的事务提交流程
1. Prepare Innodb:
a) Write prepare record to Innodb‘s log buffer
b) Sync log file to disk -- redo组提交
c) Take prepare_commit_mutex
2. "Prepare" binary log:
a) Write transaction to binary log
b) Sync binary log based on sync_binlog
3. Commit Innodb:
a) Write commit record to log
b) Release prepare_commit_mutex
c) Sync log file to disk
d) Innodb locks are released
4. "Commit" binary log:
a) Nothing necessary to do here.
不足
为保证binlog按顺序写,prepare redo阶段获取prepare_commit_mutex,直到sync redo前才释放;一次只能有一个事务可获取该mutex,阻碍了group commit;
另外,一次完整的事务提交需要调用3次fsync,效率很低;
改进
1 减少fsync
crash recovery时先查看redo log,找出prepared但没有commited或aborted的事务列表,然后检查binlog,如果binlog记录了该事务,则将其commit否则rollback;
由此可见,事务恢复时取决于binlog是否有记录,因此commit innodb时无须调用立即fsync(此时binlog已写入,就算crash也能保证事务提交);
2 细化binlog commit代码,实现组提交
在Oracle MySQL 5.6.15中,binlog group commit模块被重写,这个过程分为3个stage:flush/sync/commit;
5.6的2PC提交流程如下
1. Ask binary log (i.e. coordinator to prepare
a) Request to release locks earlier
b) Prepare Innodb (Callback to (2.a))
2. Prepare Innodb:
a) Write prepare record to Innodb log buffer
b) Sync log file to disk
3. Ask binary log (i.e. coordinator) to commit
a) Lock access to flush stage
b) Write a set of transactions to the binary log
c) Unlock access to flush stage
d) Lock access to sync stage
e) Flush the binary log to disk
f) Unlock access to sync stage
g) Lock access to commit stage
h) Commit Innodb (Callback to (4.a))
i) Unlock access to commit stage
4. Commit Innodb
a) Write a commit record to Innodb log buffer
binlog提交被细化为3个处理阶段,每一阶段都有lock保护(此时redo已经调用fsync,事务尚未提交);
这3个阶段负责批量读取binlog并调用fsync,而后以同样顺序提交事务(可选);
第一个进入处理阶段的事务担当Leader的角色,剩余的为follower,后者释放所有的latch并等待,直至leader完成commit;
leader获取所有排队等待的事务并处理,进入下一个处理阶段时,如果队列为空则仍是leader,否则降级为follower;
1. Flush Stage
leader会不断读取flush queue直到队列为空或者超时,这样允许处理过程中新加入的事务也能得到及时处理;
leader将排队的事务写入binlog buffer,当队列为空时则进入下一阶段;
超时机制避免了事务长时间等待,
2. Sync Stage
调用fsyc,一次刷新多个事务;
3. Commit Stage
提交事务,保证所有事务提交顺序同写入binlog一致(innodb hot backup); 为了提升性能,也可选择不按次序提交;
代码实现
Binlog原本实现了handlerton接口,包括commit()/rollback()等方法,5.6引入新机制
public class MYSQL_BIN_LOG: public TC_LOG
{
int open_connection(THD* thd);
int close_connection(THD* thd);
int commit(THD *thd, bool all);
int rollback(THD *thd, bool all);
int savepoint_set(THD* thd, SAVEPOINT *sv);
int savepoint_release(THD* thd, SAVEPOINT *sv);
int savepoint_rollback(THD* thd, SAVEPOINT *sv);
};
int MYSQL_BIN_LOG::commit(THD *thd, bool all)
{
/* Call batch_commit(). */
}
int MYSQL_BIN_LOG::batch_commit(THD* thd, bool all)
{
--将事务加入flush queue,第一个事务为leader,follower阻塞直至完成commit;
if (change_stage(thd, Stage_manager::FLUSH_STAGE, thd, NULL, &LOCK_log))
return finish_commit(thd->commit_error);
--将事务写入binlog
THD *flush_queue= NULL; /* Gets a pointer to the flush_queue */
error= flush_stage_queue(&wait_queue);
if (change_stage(thd, Stage_manager::SYNC_STAGE, wait_queue, &LOCK_log, &LOCK_sync))
return finish_commit(thd->commit_error);
--依据sync_binlog选项调用fsync,5.5却只能将sync_binlog=0
THD *sync_queue= NULL; /* Gets a pointer to the sync_queue */
error= sync_stage_queue(&sync_queue);
--根据opt_binlog_order_commits,可以按binlog写入顺序提交事务,也可以让线程调用handlerton->commit各自提交;
if (opt_binlog_order_commits)
{
if (change_stage(thd, Stage_manager::COMMIT_STAGE,
final_queue, &LOCK_sync, &LOCK_commit))
return finish_commit(thd);
THD *commit_queue= NULL;
error= commit_stage_queue(&commit_queue);
mysql_mutex_unlock(&LOCK_commit);
final_queue= commit_queue;
}
else
{
final_queue= sync_queue;
mysql_mutex_unlock(&LOCK_sync);
}
--通知follower,要么提交事务(opt_binlog_order_commits=false)要么通知客户端;
stage_manager.signal_done(final_queue);
return finish_commit(thd);
}
参考资料
http://dev.mysql.com/worklog/task/?id=5223
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。