这是今年3月份整理的一篇博客,在做业务过程中又有了一些新的理解,所以重新进行了梳理,增加了部分示例和绘图,尽管这里分析的是MySQL的binlog 和redo log,但是这里的两段式提交的思想在做支付场景的业务的时候经常用到。
在介绍binlog 和redo log之前,先介绍一下MySQL的总体架构,这也是后面学习的基础:
分析上图我们发现,MySQL主要分为服务层和存储引擎两大模块,binlog是server层日志,也是MySQL原生支持的日志,而redo log则是InnoDB特有的日志,下面几条结论在网上可以轻易找到,下面我们就针对如下几条进行解读:
binlog记录的是所有数据的改变,这是因为binlog是MySQL原生支持的,由于最初的时候MySQL是没有InnoDB引擎的,而redo log是InnoDB特有的,所有说binlog记录所有数据的改变信息,而redo log仅支持InnoDB;
物理日志、逻辑日志,听起来比较抽象,简单说redo log 是经过server层分析优化后的操作,在哪个数据页面做了什么修改的物理记录,binlog 简单认为记录的就是 SQL 语句(实际不仅如此,还记录该语句的反向操作等信息);为了更形象的解释这里,下面放上示例(binlog有多种格式,这只是一种):
而redo log我没有找到查看文本的方法,redolog不关心执行了什么语句,它只记录在磁盘的哪个位置做了什么修改(实际远不止如此,可查看该篇文章http://mysql.taobao.org/monthly/2017/09/07)。
首先讲redo log,我们每次执行的语句并不是执行完就写入MySQL,而是先写入到redo log中就算该条语句执行完毕,然后再写入到实际的MySQL存储文件中,其实大多数数据库都是这样的做的,其好处时可以提高执行效率,顺序的写入到一个临时文件,然后批量的更新到存储文件远比直接更新到存储文件效率要高的多。redo log是固定大小的四个文件,每个文件大小可以设置,所以redo log不是持久的,而是循环写入,一个指针记录可写入的开始位置,一个指针记录需要写入存储文件的开始位置。具体如下图所示:
正是由于redo log的WAL机制,使InnoDB具有crash-safe的能力,即使数据库异常重启也不会导致已提交的数据丢失。
换种说法,binlog是在一个事务完全提交后才会写入,但是redo log在事务未提交前就会写入,可以简单的理解为实时写入。这里也就引出了另外一个问题,两个日志都记录了一些操作状态,那么两个日志是如何保持一致性的呢?答案是两段式提交。
我们可以想象这样一个场景,由于机器故障导致丢了一周的数据,值得庆幸的时我们存储了近一周的binlog日志,并且前一周的整库备份也恰好存在,我们可以从整库备份的时间开始取出binlog按照顺序执行到最后一条,那么问题来了,最后一条的状态能否保证redo log和binlog记录的状态是一致的呢。仔细分析发现无论先写binlog还是redo log,如果机器故障恰好在写入binlog和redo log之间,那么两个日志记录的数据库状态都是不一致的。
而两段式提交又是什么情况呢?
1)假设在位置1故障,在恢复到最后一条记录时发现redo log未提交,binlog没有该次操作记录,则放弃恢复,认为最后一次操作未完成;
2)如果在位置2处故障,在恢复到最后一条记录时发现redo未提交,但是binlog存在该次操作,则直接将redo log提交并恢复记录。
这样就保证了binlog和redo log记录的状态永远保持了一致,这种思想实际上在要求一致性的场景下经常被使用。
这里产生另外一个疑问,为什么不能先写redo log再写binlog,如果发现binlog没有该次操作直接不执行不就可以了吗,这样岂不也保证一致了? 这里就涉及到redo log和binlog的设计了,前面已经说过binlog在事务执行完才会写入,而redo log是实时写入的,两段式场景时我们认为redo log提交后就不可回滚事务,因为该次事务已经执行完了,如果再回滚很有可能会覆盖其它事务。假如redo log写入成功,binlog写入失败,MySQL依靠redo log实现crash-safa,这时候发现binlog写入失败,redo log又不能回滚,就会出现不一致的情况。
那为什么不能删除binlog呢而直接使用redo log呢? 因为binlog是持续写入的,而redo log是循环写入的,并不能持续的备份。同理也不能删除redo log而只保留binlog呢,因为binlog的设计之初并不能支持crash-safa。
最后安利一下极客时间《MySQL实战45讲》,作者讲的很细,学完收获很大。