binlog 和 redo log一致性

一致性问题:

    MySQL为了兼容其它非事物引擎的复制,在server层面引入了 binlog, 它可以记录所有引擎中的修改操作,因而可以对所有的引擎使用复制功能; 但是引入了binlog,会导致一个问题——binlog和redo log的一致性问题:

  • 一个事务的提交必须写redo log和binlog,那么二者如何协调一致呢?
  • 事务的提交以哪一个log为标准?
  • 如何判断事务提交?
  • 事务崩溃恢复如何进行?

解决方案

    自 5.1 之后,binlog 和 innodb 采用类似两阶段提交的方式,不过不支持 group commit;

    在 5.6 中,引入binlog组提交,将 binlog 的 commit 阶段分为三个阶段:flush stage、sync stage 以及 commit stage。

两阶段提交(2PC):

第一阶段:InnoDB prepare,持有prepare_commit_mutex,并且write/sync redo log; 将回滚段设置为Prepared状态,binlog不作任何操作;
第二阶段:包含两步,1> write/sync Binlog; 2> InnoDB commit (写入COMMIT标记后释放prepare_commit_mutex);

    以 binlog 的写入与否作为事务提交成功与否的标志,innodb commit标志并不是事务成功与否的标志。因为此时的事务崩溃恢复场景如下:

1> 一阶段prepare 成功 redo log落盘完成,二阶段binlog写入失败,崩溃恢复时回滚该事物。

2> 一阶段prepare 成功 redo log落盘完成,二阶段binlog写入成功,commit失败,崩溃恢复时扫描最后一个Binlog文件,提取其中的xid,InnoDB维持了状态为Prepare的事务链表,将这些事务的xid和Binlog中记录的xid做比较,在Binlog中存在,则提交该事物。

    通过这种方式,可以让InnoDB和Binlog中的事务状态保持一致。如果在写入innodb commit标志时崩溃,则恢复时,会重新对commit标志进行写入;

组提交(group commit):

    上面的事务的两阶段提交过程是5.6之前版本中的实现,有严重的缺陷。当sync_binlog=1时,很明显上述的第二阶段中的 write/sync binlog会成为瓶颈,而且还是持有全局大锁(prepare_commit_mutex: prepare 和 commit共用一把锁),这会导致性能急剧下降。解决办法就是MySQL5.6中的 binlog组提交。

    5.6 引入了组提交,并将提交过程分成 Flush stage、Sync stage、Commit stage 三个阶段。

InnoDB, Prepare
    SQL已经成功执行并生成了相应的redo和undo内存日志(对多个线程的redo合并写入磁盘);
Binlog, Flush Stage
    所有已经注册线程都将写入binlog缓存;
Binlog, Sync Stage
    binlog缓存将sync到磁盘,sync_binlog=1时该队列中所有事务的binlog将永久写入磁盘(对多个线程的binlog合并写入磁盘);
InnoDB, Commit stage
    leader根据顺序调用存储引擎提交事务;

原理:

  • 多个并发需要提交的事务共享一次fsync操作来进行数据的持久化
  • 将fsync操作的开销平摊到多个并发的事务上去
  • group commit 不是在任何时候都能发挥作用,要有足够多并发的需要提交的事务

实现:

  • 多个并发提交的事务在写redo log或binlog前会被加入到一个队列
  • 队列头部的事务所在的线程称为leader线程,其它事务所在的线程称为follower线程
  • leader线程负责为队列中所有的事务进行写binlog操作,此时,所有的follower线程处于等待状态
  • 然后leader线程调用一次fsync操作,将binlog持久化
  • 最后通知follower线程可以继续往下执行

参数:

binlog_group_commit_sync_delay=N

定时发车,在等待N 微秒后,进行binlog刷盘操作

binlog_group_commit_sync_no_delay_count=N

人满发车,达到最大事务等待数量,开始binlog刷盘,忽略定时发车

疑惑

看到这里,有人可能会想,既然生产环境一般建议将 innodb_flush_log_at_trx_commit 设置为 1,也就是说每次更新数据时,最终还是要将 redo log 写入到磁盘,也就是还是会发生一次磁盘 IO,而我为什么不直接停止使用 redo log,而在每次更新数据时,也不要直接更新内存了,直接将数据更新到磁盘,这样也是发生了一次磁盘 IO,何必引入 redo log 这一机制呢?

首先引入 redo log 机制是十分必要的。因为写 redo log 时,我们将 redo log 日志追加到文件末尾,虽然也是一次磁盘 IO,但是这是顺序写操作(不需要移动磁头);而对于直接将数据更新到磁盘,涉及到的操作是将 buffer pool 中缓存页写入到磁盘上的数据页上,由于涉及到寻找数据页在磁盘的哪个地方,这个操作发生的是随机写操作(需要移动磁头),相比于顺序写操作,磁盘的随机写操作性能消耗更大,花费的时间更长,因此 redo log 机制更优,能提升 MySQL 的性能。

从另一方面来讲,通常一次更新操作,我们往往只会涉及到修改几个字节的数据,而如果因为仅仅修改几个字节的数据,就将整个数据页写入到磁盘(无论是磁盘还是 buffer pool,他们管理数据的单位都是以页为单位),这个代价未免也太了(每个数据页默认是 16KB),而一条 redo log 日志的大小可能就只有几个字节,因此每次磁盘 IO 写入的数据量更小,那么耗时也会更短。 综合来看,redo log 机制的引入,在提高 MySQL 性能的同时,也保证了数据的可靠性。

参考:MySQL一条更新sql的执行流程,以及日志的两阶段提交,以及崩溃恢复流程和组提交_刘Java的博客-CSDN博客

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值